using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Profiling;
namespace UnityEngine.Polybrush
{
///
/// Static helper functions for working with meshes.
///
internal static class PolyMeshUtility
{
///
/// Duplicate "src" and return the copy.
///
///
///
internal static Mesh DeepCopy(Mesh src)
{
//null checks
if (src == null)
{
return null;
}
Mesh dst = new Mesh();
Copy(src, dst);
return dst;
}
///
/// Copy "src" mesh values to "dest"
///
/// destination
/// source
internal static void Copy(Mesh src, Mesh dst)
{
//null checks
if(dst == null || src == null)
{
return;
}
dst.Clear();
dst.vertices = src.vertices;
List uvs = new List();
src.GetUVs(0, uvs); dst.SetUVs(0, uvs);
src.GetUVs(1, uvs); dst.SetUVs(1, uvs);
src.GetUVs(2, uvs); dst.SetUVs(2, uvs);
src.GetUVs(3, uvs); dst.SetUVs(3, uvs);
dst.normals = src.normals;
dst.tangents = src.tangents;
dst.boneWeights = src.boneWeights;
dst.colors = src.colors;
dst.colors32 = src.colors32;
dst.bindposes = src.bindposes;
dst.subMeshCount = src.subMeshCount;
for(int i = 0; i < src.subMeshCount; i++)
dst.SetIndices(src.GetIndices(i), src.GetTopology(i), i);
dst.name = Util.IncrementPrefix("z", src.name);
}
///
/// Creates a new mesh using only the "src" positions, normals, and a new color array.
///
/// source Mesh
///
internal static Mesh CreateOverlayMesh(PolyMesh src)
{
//null checks
if(src == null)
{
return null;
}
Mesh m = new Mesh();
m.name = "Overlay Mesh: " + src.name;
m.vertices = src.vertices;
m.normals = src.normals;
m.colors = Util.Fill(new Color(0f, 0f, 0f, 0f), m.vertexCount);
m.subMeshCount = src.subMeshCount;
for(int i = 0; i < src.subMeshCount; i++)
{
SubMesh subMesh = src.subMeshes[i];
MeshTopology subMeshTopology = subMesh.topology;
int[] subMeshIndices = subMesh.indexes;
if (subMeshTopology == MeshTopology.Triangles)
{
int[] tris = subMeshIndices;
int[] lines = new int[tris.Length * 2];
int index = 0;
for(int n = 0; n < tris.Length; n+=3)
{
lines[index++] = tris[n+0];
lines[index++] = tris[n+1];
lines[index++] = tris[n+1];
lines[index++] = tris[n+2];
lines[index++] = tris[n+2];
lines[index++] = tris[n+0];
}
m.SetIndices(lines, MeshTopology.Lines, i);
}
else
{
m.SetIndices(subMeshIndices, subMeshTopology, i);
}
}
return m;
}
private static readonly Color clear = new Color(0,0f,0,0f);
static readonly Vector2[] k_VertexBillboardUV0Content = new Vector2[]
{
Vector3.zero,
Vector3.right,
Vector3.up,
Vector3.one
};
static readonly Vector2[] k_VertexBillboardUV2Content = new Vector2[]
{
-Vector3.up-Vector3.right,
-Vector3.up+Vector3.right,
Vector3.up-Vector3.right,
Vector3.up+Vector3.right,
};
internal static Mesh CreateVertexBillboardMesh(PolyMesh src, int[][] common)
{
if (src == null || common == null)
return null;
int vertexCount = System.Math.Min(ushort.MaxValue / 4, common.Count());
Vector3[] positions = new Vector3[vertexCount * 4];
Vector2[] uv0 = new Vector2[vertexCount * 4];
Vector2[] uv2 = new Vector2[vertexCount * 4];
Color[] colors = new Color[vertexCount * 4];
int[] tris = new int[vertexCount * 6];
int n = 0;
int t = 0;
Vector3[] v = src.vertices;
for (int i = 0; i < vertexCount; i++)
{
int tri = common[i][0];
positions[t + 0] = v[tri];
positions[t + 1] = v[tri];
positions[t + 2] = v[tri];
positions[t + 3] = v[tri];
uv0[t + 0] = k_VertexBillboardUV0Content[0];
uv0[t + 1] = k_VertexBillboardUV0Content[1];
uv0[t + 2] = k_VertexBillboardUV0Content[2];
uv0[t + 3] = k_VertexBillboardUV0Content[3];
uv2[t + 0] = k_VertexBillboardUV2Content[0];
uv2[t + 1] = k_VertexBillboardUV2Content[1];
uv2[t + 2] = k_VertexBillboardUV2Content[2];
uv2[t + 3] = k_VertexBillboardUV2Content[3];
tris[n + 0] = t + 0;
tris[n + 1] = t + 1;
tris[n + 2] = t + 2;
tris[n + 3] = t + 1;
tris[n + 4] = t + 3;
tris[n + 5] = t + 2;
colors[t + 0] = clear;
colors[t + 1] = clear;
colors[t + 2] = clear;
colors[t + 3] = clear;
t += 4;
n += 6;
}
Mesh m = new Mesh();
m.vertices = positions;
m.uv = uv0;
m.uv2 = uv2;
m.colors = colors;
m.triangles = tris;
return m;
}
///
/// Builds a lookup table for each vertex index and it's average normal with other vertices sharing a position.
///
///
///
internal static Dictionary GetSmoothNormalLookup(PolyMesh mesh)
{
//null checks
if (mesh == null)
{
return null;
}
Vector3[] n = mesh.normals;
Dictionary normals = new Dictionary();
if(n == null || n.Length != mesh.vertexCount)
return normals;
int[][] groups = GetCommonVertices(mesh);
Vector3 avg = Vector3.zero;
Vector3 a = Vector3.zero;
foreach(var group in groups)
{
avg.x = 0f;
avg.y = 0f;
avg.z = 0f;
foreach(int i in group)
{
a = n[i];
avg.x += a.x;
avg.y += a.y;
avg.z += a.z;
}
avg /= (float) group.Count();
foreach(int i in group)
normals.Add(i, avg);
}
return normals;
}
struct CommonVertexCache
{
int m_Hash;
public int[][] indices;
public CommonVertexCache(Mesh mesh)
{
m_Hash = GetHash(mesh);
Vector3[] v = mesh.vertices;
// this is _really_ slow
int[] t = Util.Fill((x) => { return x; }, v.Length);
indices = t.ToLookup(x => (RndVec3)v[x])
.Select(y => y.ToArray())
.ToArray();
}
public bool IsValidForMesh(Mesh mesh)
{
return m_Hash == GetHash(mesh);
}
static int GetHash(Mesh mesh)
{
unchecked
{
int hash = 27 * 29 + mesh.vertexCount;
for(int i = 0, c = mesh.subMeshCount; i < c; i++)
hash = hash * 29 + (int) mesh.GetIndexCount(i);
return hash;
}
}
}
///
/// Store a temporary cache of common vertex indices.
///
static Dictionary commonVerticesCache = new Dictionary();
///
/// Builds a list with each vertex index and a list of all other vertices sharing a position.
///
///
/// key: Index in vertices array
/// value: List of other indices in positions array that share a point with this index.
///
internal static int[][] GetCommonVertices(PolyMesh mesh)
{
//null checks
if (mesh == null)
return null;
CommonVertexCache cache;
if(commonVerticesCache.TryGetValue(mesh, out cache))
{
if (cache.IsValidForMesh(mesh.mesh))
return cache.indices;
}
if(!commonVerticesCache.ContainsKey(mesh))
commonVerticesCache.Add(mesh, cache = new CommonVertexCache(mesh.mesh));
else
commonVerticesCache[mesh] = cache = new CommonVertexCache(mesh.mesh);
return cache.indices;
}
internal static List GetEdges(PolyMesh m)
{
Dictionary lookup = GetCommonVertices(m).GetCommonLookup();
return GetEdges(m, lookup);
}
internal static List GetEdges(PolyMesh m, Dictionary lookup)
{
int[] tris = m.GetTriangles();
int count = tris.Length;
List edges = new List(count);
for(int i = 0; i < count; i += 3)
{
edges.Add( new CommonEdge(tris[i+0], tris[i+1], lookup[tris[i+0]], lookup[tris[i+1]]) );
edges.Add( new CommonEdge(tris[i+1], tris[i+2], lookup[tris[i+1]], lookup[tris[i+2]]) );
edges.Add( new CommonEdge(tris[i+2], tris[i+0], lookup[tris[i+2]], lookup[tris[i+0]]) );
}
return edges;
}
internal static HashSet GetEdgesDistinct(PolyMesh mesh, out List duplicates)
{
//null checks
if (mesh == null)
{
duplicates = null;
return null;
}
Dictionary lookup = GetCommonVertices(mesh).GetCommonLookup();
return GetEdgesDistinct(mesh, lookup, out duplicates);
}
private static HashSet GetEdgesDistinct(PolyMesh m, Dictionary lookup, out List duplicates)
{
int[] tris = m.GetTriangles();
int count = tris.Length;
HashSet edges = new HashSet();
duplicates = new List();
for(int i = 0; i < count; i += 3)
{
CommonEdge a = new CommonEdge(tris[i+0], tris[i+1], lookup[tris[i+0]], lookup[tris[i+1]]);
CommonEdge b = new CommonEdge(tris[i+1], tris[i+2], lookup[tris[i+1]], lookup[tris[i+2]]);
CommonEdge c = new CommonEdge(tris[i+2], tris[i+0], lookup[tris[i+2]], lookup[tris[i+0]]);
if(!edges.Add(a))
duplicates.Add(a);
if(!edges.Add(b))
duplicates.Add(b);
if(!edges.Add(c))
duplicates.Add(c);
}
return edges;
}
///
/// Returns all vertex indices that are on an open edge.
///
///
///
internal static HashSet GetNonManifoldIndices(PolyMesh mesh)
{
if (mesh == null)
return null;
List duplicates;
HashSet edges = GetEdgesDistinct(mesh, out duplicates);
edges.ExceptWith(duplicates);
HashSet hash = CommonEdge.ToHashSet(edges);
return hash;
}
///
/// Builds a lookup with each vertex index and a list of all neighboring indices.
///
///
///
internal static Dictionary GetAdjacentVertices(PolyMesh mesh)
{
//null checks
if (mesh == null)
{
return null;
}
int[][] common = GetCommonVertices(mesh);
Dictionary lookup = common.GetCommonLookup();
List edges = GetEdges(mesh, lookup);
List> map = new List>();
for(int i = 0; i < common.Count(); i++)
map.Add(new List());
for(int i = 0; i < edges.Count; i++)
{
map[edges[i].cx].Add(edges[i].y);
map[edges[i].cy].Add(edges[i].x);
}
Dictionary adjacent = new Dictionary();
IEnumerable distinctTriangles = mesh.GetTriangles().Distinct();
foreach(int i in distinctTriangles)
adjacent.Add(i, map[lookup[i]].ToArray());
return adjacent;
}
static Dictionary>> adjacentTrianglesCache = new Dictionary>>();
///
/// Returns a dictionary where each PolyEdge is mapped to a list of triangle indices that share that edge.
/// To translate triangle list to vertex indices, multiply by 3 and take those indices (ex, triangles[index+{0,1,2}])
///
/// mesh to use
/// see summary
internal static Dictionary> GetAdjacentTriangles(PolyMesh mesh)
{
//null checks
if (mesh == null)
{
return null;
}
int len = mesh.GetTriangles().Length;
if(len % 3 !=0 || len / 3 == mesh.vertexCount)
return new Dictionary>();
Dictionary> lookup = null;
// @todo - should add some checks to make sure triangle structure hasn't changed
if(adjacentTrianglesCache.TryGetValue(mesh, out lookup) && lookup.Count == mesh.vertexCount)
return lookup;
if (adjacentTrianglesCache.ContainsKey(mesh))
adjacentTrianglesCache.Remove(mesh);
int subMeshCount = mesh.subMeshCount;
lookup = new Dictionary>();
List connections;
for(int n = 0; n < subMeshCount; n++)
{
int[] tris = mesh.subMeshes[n].indexes;
for(int i = 0; i < tris.Length; i+=3)
{
int index = i/3;
PolyEdge a = new PolyEdge(tris[i ], tris[i+1]);
PolyEdge b = new PolyEdge(tris[i+1], tris[i+2]);
PolyEdge c = new PolyEdge(tris[i+2], tris[i ]);
if(lookup.TryGetValue(a, out connections))
connections.Add(index);
else
lookup.Add(a, new List(){index});
if(lookup.TryGetValue(b, out connections))
connections.Add(index);
else
lookup.Add(b, new List(){index});
if(lookup.TryGetValue(c, out connections))
connections.Add(index);
else
lookup.Add(c, new List(){index});
}
}
adjacentTrianglesCache.Add(mesh, lookup);
return lookup;
}
private static Dictionary commonNormalsCache = new Dictionary();
///
/// Vertices that are common, form a seam, and should be smoothed.
///
///
///
internal static int[][] GetSmoothSeamLookup(PolyMesh mesh)
{
//null checks
if (mesh == null)
{
return null;
}
Vector3[] normals = mesh.normals;
if(normals == null)
return null;
int[][] lookup = null;
if(commonNormalsCache.TryGetValue(mesh, out lookup))
return lookup;
int[][] common = GetCommonVertices(mesh);
var z = common
.SelectMany(x => x.GroupBy( i => (RndVec3)normals[i] ))
.Where(n => n.Count() > 1)
.Select(t => t.ToArray())
.ToArray();
commonNormalsCache.Add(mesh, z);
return z;
}
///
/// Recalculates a mesh's normals while retaining smoothed common vertices.
///
///
internal static void RecalculateNormals(PolyMesh mesh)
{
//null checks
if (mesh == null)
return;
int[][] smooth = GetSmoothSeamLookup(mesh);
mesh.RecalculateNormals();
if(smooth != null)
{
Vector3[] normals = mesh.normals;
for (int i = 0; i < smooth.Length; ++i)
{
int[] l = smooth[i];
Vector3 n = Math.Average(normals, l);
for (int j = 0; j < l.Length; ++j)
normals[l[j]] = n;
}
mesh.normals = normals;
}
}
}
}