using UnityEngine;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections;
using System.Text;
namespace UnityEngine.ProBuilder
{
///
/// Provides functions for generating mesh attributes and utilities.
///
public static class MeshUtility
{
///
/// Create an array of @"UnityEngine.ProBuilder.Vertex" values that are ordered as individual triangles. This modifies the source mesh to match the new individual triangles format.
///
/// The mesh to extract vertices from, and apply per-triangle topology to.
/// A @"UnityEngine.ProBuilder.Vertex" array of the per-triangle vertices.
internal static Vertex[] GeneratePerTriangleMesh(Mesh mesh)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
Vertex[] vertices = mesh.GetVertices();
int smc = mesh.subMeshCount;
Vertex[] tv = new Vertex[mesh.triangles.Length];
int[][] triangles = new int[smc][];
int triIndex = 0;
for (int s = 0; s < smc; s++)
{
triangles[s] = mesh.GetTriangles(s);
int tl = triangles[s].Length;
for (int i = 0; i < tl; i++)
{
tv[triIndex++] = new Vertex(vertices[triangles[s][i]]);
triangles[s][i] = triIndex - 1;
}
}
Vertex.SetMesh(mesh, tv);
mesh.subMeshCount = smc;
for (int s = 0; s < smc; s++)
mesh.SetTriangles(triangles[s], s);
return tv;
}
///
/// Generates tangents and applies them on the specified mesh.
///
/// The mesh target.
public static void GenerateTangent(Mesh mesh)
{
if (mesh == null)
throw new System.ArgumentNullException("mesh");
// http://answers.unity3d.com/questions/7789/calculating-tangents-vector4.html
// speed up math by copying the mesh arrays
int[] triangles = mesh.triangles;
Vector3[] vertices = mesh.vertices;
Vector2[] uv = mesh.uv;
Vector3[] normals = mesh.normals;
//variable definitions
int triangleCount = triangles.Length;
int vertexCount = vertices.Length;
Vector3[] tan1 = new Vector3[vertexCount];
Vector3[] tan2 = new Vector3[vertexCount];
Vector4[] tangents = new Vector4[vertexCount];
for (long a = 0; a < triangleCount; a += 3)
{
long i1 = triangles[a + 0];
long i2 = triangles[a + 1];
long i3 = triangles[a + 2];
Vector3 v1 = vertices[i1];
Vector3 v2 = vertices[i2];
Vector3 v3 = vertices[i3];
Vector2 w1 = uv[i1];
Vector2 w2 = uv[i2];
Vector2 w3 = uv[i3];
float x1 = v2.x - v1.x;
float x2 = v3.x - v1.x;
float y1 = v2.y - v1.y;
float y2 = v3.y - v1.y;
float z1 = v2.z - v1.z;
float z2 = v3.z - v1.z;
float s1 = w2.x - w1.x;
float s2 = w3.x - w1.x;
float t1 = w2.y - w1.y;
float t2 = w3.y - w1.y;
float r = 1.0f / (s1 * t2 - s2 * t1);
Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
tan1[i1] += sdir;
tan1[i2] += sdir;
tan1[i3] += sdir;
tan2[i1] += tdir;
tan2[i2] += tdir;
tan2[i3] += tdir;
}
for (long a = 0; a < vertexCount; ++a)
{
Vector3 n = normals[a];
Vector3 t = tan1[a];
Vector3.OrthoNormalize(ref n, ref t);
tangents[a].x = t.x;
tangents[a].y = t.y;
tangents[a].z = t.z;
tangents[a].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f;
}
mesh.tangents = tangents;
}
///
/// Returns a new mesh containing all attributes and values copied from the specified source mesh.
///
/// The mesh to copy from.
/// A new object with the same values as the source mesh.
public static Mesh DeepCopy(Mesh source)
{
Mesh m = new Mesh();
CopyTo(source, m);
return m;
}
///
/// Copies mesh attribute values from one mesh to another.
///
/// The mesh from which to copy attribute values.
/// The destination mesh to copy attribute values to.
/// Throws if source or destination is null.
public static void CopyTo(Mesh source, Mesh destination)
{
if (source == null)
throw new System.ArgumentNullException("source");
if (destination == null)
throw new System.ArgumentNullException("destination");
Vector3[] v = new Vector3[source.vertices.Length];
int[][] t = new int[source.subMeshCount][];
Vector2[] u = new Vector2[source.uv.Length];
Vector2[] u2 = new Vector2[source.uv2.Length];
Vector4[] tan = new Vector4[source.tangents.Length];
Vector3[] n = new Vector3[source.normals.Length];
Color32[] c = new Color32[source.colors32.Length];
System.Array.Copy(source.vertices, v, v.Length);
for (int i = 0; i < t.Length; i++)
t[i] = source.GetTriangles(i);
System.Array.Copy(source.uv, u, u.Length);
System.Array.Copy(source.uv2, u2, u2.Length);
System.Array.Copy(source.normals, n, n.Length);
System.Array.Copy(source.tangents, tan, tan.Length);
System.Array.Copy(source.colors32, c, c.Length);
destination.Clear();
destination.name = source.name;
destination.vertices = v;
destination.subMeshCount = t.Length;
for (int i = 0; i < t.Length; i++)
destination.SetTriangles(t[i], i);
destination.uv = u;
destination.uv2 = u2;
destination.tangents = tan;
destination.normals = n;
destination.colors32 = c;
}
///
/// Get a mesh attribute from either the MeshFilter.sharedMesh or the MeshRenderer.additionalVertexStreams mesh. The additional vertex stream mesh has priority.
///
/// The type of the attribute to fetch.
/// The GameObject with the MeshFilter and (optional) MeshRenderer to search for mesh attributes.
/// The function used to extract mesh attribute.
/// A List of the mesh attribute values from the Additional Vertex Streams mesh if it exists and contains the attribute, or the MeshFilter.sharedMesh attribute values.
internal static T GetMeshChannel(GameObject gameObject, Func attributeGetter) where T : IList
{
if (gameObject == null)
throw new System.ArgumentNullException("gameObject");
if (attributeGetter == null)
throw new System.ArgumentNullException("attributeGetter");
MeshFilter mf = gameObject.GetComponent();
Mesh mesh = mf != null ? mf.sharedMesh : null;
T res = default(T);
if (mesh == null)
return res;
int vertexCount = mesh.vertexCount;
#if !UNITY_4_6 && !UNITY_4_7
MeshRenderer renderer = gameObject.GetComponent();
Mesh vertexStream = renderer != null ? renderer.additionalVertexStreams : null;
if (vertexStream != null)
{
res = attributeGetter(vertexStream);
if (res != null && res.Count == vertexCount)
return res;
}
#endif
res = attributeGetter(mesh);
return res != null && res.Count == vertexCount ? res : default(T);
}
static void PrintAttribute(StringBuilder sb, string title, IEnumerable attrib, string fmt)
{
sb.AppendLine($" - {title}");
if (attrib != null && attrib.Any())
{
foreach (var value in attrib)
sb.AppendLine(string.Format($" {fmt}", value));
}
else
{
sb.AppendLine("\tnull");
}
}
///
/// Prints a detailed string summary of the mesh attributes.
///
/// The mesh to print information for.
/// A tab-delimited string (positions, normals, colors, tangents, and UV coordinates).
public static string Print(Mesh mesh)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
StringBuilder sb = new StringBuilder();
Vector3[] positions = mesh.vertices;
Vector3[] normals = mesh.normals;
Color[] colors = mesh.colors;
Vector4[] tangents = mesh.tangents;
List uv0 = new List();
Vector2[] uv2 = mesh.uv2;
List uv3 = new List();
List uv4 = new List();
mesh.GetUVs(0, uv0);
mesh.GetUVs(2, uv3);
mesh.GetUVs(3, uv4);
sb.AppendLine($"# Sanity Check");
sb.AppendLine(MeshUtility.SanityCheck(mesh));
sb.AppendLine($"# Attributes ({mesh.vertexCount})");
PrintAttribute(sb, $"positions ({positions.Length})", positions, "pos: {0:F2}");
PrintAttribute(sb, $"normals ({normals.Length})", normals, "nrm: {0:F2}");
PrintAttribute(sb, $"colors ({colors.Length})", colors, "col: {0:F2}");
PrintAttribute(sb, $"tangents ({tangents.Length})", tangents, "tan: {0:F2}");
PrintAttribute(sb, $"uv0 ({uv0.Count})", uv0, "uv0: {0:F2}");
PrintAttribute(sb, $"uv2 ({uv2.Length})", uv2, "uv2: {0:F2}");
PrintAttribute(sb, $"uv3 ({uv3.Count})", uv3, "uv3: {0:F2}");
PrintAttribute(sb, $"uv4 ({uv4.Count})", uv4, "uv4: {0:F2}");
sb.AppendLine("# Topology");
for (int i = 0; i < mesh.subMeshCount; i++)
{
var topo = mesh.GetTopology(i);
var submesh = mesh.GetIndices(i);
sb.AppendLine($" Submesh[{i}] ({topo})");
switch (topo)
{
case MeshTopology.Points:
for (int n = 0; n < submesh.Length; n += 1)
sb.AppendLine(string.Format("\t{0}", submesh[n]));
break;
case MeshTopology.Lines:
for (int n = 0; n < submesh.Length; n += 2)
sb.AppendLine(string.Format("\t{0}, {1}", submesh[n], submesh[n + 1]));
break;
case MeshTopology.Triangles:
for (int n = 0; n < submesh.Length; n += 3)
sb.AppendLine(string.Format("\t{0}, {1}, {2}", submesh[n], submesh[n + 1], submesh[n + 2]));
break;
case MeshTopology.Quads:
for (int n = 0; n < submesh.Length; n += 4)
sb.AppendLine(string.Format("\t{0}, {1}, {2}, {3}", submesh[n], submesh[n + 1], submesh[n + 2], submesh[n + 3]));
break;
}
}
return sb.ToString();
}
///
/// Returns the number of indices this mesh contains.
///
/// The source mesh to sum submesh index counts from.
/// The count of all indices contained within this mesh's submeshes.
public static uint GetIndexCount(Mesh mesh)
{
uint sum = 0;
if (mesh == null)
return sum;
for (int i = 0, c = mesh.subMeshCount; i < c; i++)
sum += mesh.GetIndexCount(i);
return sum;
}
///
/// Returns the number of triangles or quads this mesh contains. No other mesh topologies are considered.
///
/// The source mesh to sum submesh primitive counts from.
/// The count of all triangles or quads contained within this mesh's submeshes.
public static uint GetPrimitiveCount(Mesh mesh)
{
uint sum = 0;
if (mesh == null)
return sum;
for (int i = 0, c = mesh.subMeshCount; i < c; i++)
{
if (mesh.GetTopology(i) == MeshTopology.Triangles)
sum += mesh.GetIndexCount(i) / 3;
else if (mesh.GetTopology(i) == MeshTopology.Quads)
sum += mesh.GetIndexCount(i) / 4;
}
return sum;
}
///
/// Compiles a from a ProBuilderMesh.
///
/// The source mesh.
/// The destination `UnityEngine.Mesh`.
/// True to try to create topology that matches the requested format. If the method can't create topology using the requested format, it uses triangles where necessary.
public static void Compile(ProBuilderMesh probuilderMesh, Mesh targetMesh, MeshTopology preferredTopology = MeshTopology.Triangles)
{
if (probuilderMesh == null)
throw new ArgumentNullException("probuilderMesh");
if (targetMesh == null)
throw new ArgumentNullException("targetMesh");
targetMesh.Clear();
targetMesh.vertices = probuilderMesh.positionsInternal;
targetMesh.uv = probuilderMesh.texturesInternal;
if (probuilderMesh.HasArrays(MeshArrays.Texture2))
{
List uvChannel = new List();
probuilderMesh.GetUVs(2, uvChannel);
targetMesh.SetUVs(2, uvChannel);
}
if (probuilderMesh.HasArrays(MeshArrays.Texture3))
{
List uvChannel = new List();
probuilderMesh.GetUVs(3, uvChannel);
targetMesh.SetUVs(3, uvChannel);
}
targetMesh.normals = probuilderMesh.GetNormals();
targetMesh.tangents = probuilderMesh.GetTangents();
if (probuilderMesh.HasArrays(MeshArrays.Color))
targetMesh.colors = probuilderMesh.colorsInternal;
var materialCount = probuilderMesh.GetComponent().sharedMaterials.Length;
var submeshes = Submesh.GetSubmeshes(probuilderMesh.facesInternal, materialCount, preferredTopology);
targetMesh.subMeshCount = submeshes.Length;
for (int i = 0; i < targetMesh.subMeshCount; i++)
targetMesh.SetIndices(submeshes[i].m_Indexes, submeshes[i].m_Topology, i, false);
}
///
/// Creates a new array of vertices with values from a object.
///
/// The source mesh.
/// An array of vertices.
public static Vertex[] GetVertices(this Mesh mesh)
{
if (mesh == null)
return null;
int vertexCount = mesh.vertexCount;
Vertex[] v = new Vertex[vertexCount];
Vector3[] positions = mesh.vertices;
Color[] colors = mesh.colors;
Vector3[] normals = mesh.normals;
Vector4[] tangents = mesh.tangents;
Vector2[] uv0s = mesh.uv;
Vector2[] uv2s = mesh.uv2;
List uv3s = new List();
List uv4s = new List();
mesh.GetUVs(2, uv3s);
mesh.GetUVs(3, uv4s);
bool _hasPositions = positions != null && positions.Length == vertexCount;
bool _hasColors = colors != null && colors.Length == vertexCount;
bool _hasNormals = normals != null && normals.Length == vertexCount;
bool _hasTangents = tangents != null && tangents.Length == vertexCount;
bool _hasUv0 = uv0s != null && uv0s.Length == vertexCount;
bool _hasUv2 = uv2s != null && uv2s.Length == vertexCount;
bool _hasUv3 = uv3s.Count == vertexCount;
bool _hasUv4 = uv4s.Count == vertexCount;
for (int i = 0; i < vertexCount; i++)
{
v[i] = new Vertex();
if (_hasPositions)
v[i].position = positions[i];
if (_hasColors)
v[i].color = colors[i];
if (_hasNormals)
v[i].normal = normals[i];
if (_hasTangents)
v[i].tangent = tangents[i];
if (_hasUv0)
v[i].uv0 = uv0s[i];
if (_hasUv2)
v[i].uv2 = uv2s[i];
if (_hasUv3)
v[i].uv3 = uv3s[i];
if (_hasUv4)
v[i].uv4 = uv4s[i];
}
return v;
}
///
/// Merges coincident vertices where possible, optimizing the vertex count of a object.
///
/// The mesh to optimize.
///
/// Specify an array of objects to use instead of extracting attributes from the mesh.
///
/// This is a performance optimization for when this array already exists. If not specified, ProBuilder generates this array automatically.
///
public static void CollapseSharedVertices(Mesh mesh, Vertex[] vertices = null)
{
if (mesh == null)
throw new System.ArgumentNullException("mesh");
bool hasCollapsedVertices = vertices != null;
if(vertices == null)
vertices = mesh.GetVertices();
int smc = mesh.subMeshCount;
List> subVertices = new List>();
int[][] tris = new int[smc][];
int subIndex = 0;
for (int i = 0; i < smc; ++i)
{
tris[i] = mesh.GetTriangles(i);
Dictionary newVertices = new Dictionary();
for (int n = 0; n < tris[i].Length; n++)
{
Vertex v = vertices[tris[i][n]];
int index;
if (newVertices.TryGetValue(v, out index))
{
tris[i][n] = index;
}
else
{
tris[i][n] = subIndex;
newVertices.Add(v, subIndex);
subIndex++;
}
}
subVertices.Add(newVertices);
}
Vertex[] collapsed = subVertices.SelectMany(x => x.Keys).ToArray();
//Check if new vertices have been collapsed
hasCollapsedVertices |= (collapsed.Length != vertices.Length);
if(hasCollapsedVertices)
{
Vertex.SetMesh(mesh, collapsed);
mesh.subMeshCount = smc;
for(int i = 0; i < smc; i++)
mesh.SetTriangles(tris[i], i);
}
}
///
/// Scales mesh vertices to fit within a bounding box.
///
/// The mesh to scale.
/// The bounding box that defines the mesh's shape.
/// The new size to fit mesh contents within.
public static void FitToSize(ProBuilderMesh mesh, Bounds currentSize, Vector3 sizeToFit)
{
if (mesh.vertexCount < 1)
return;
var scale = Math.Abs(sizeToFit).DivideBy(currentSize.size);
if (scale == Vector3.one || scale == Vector3.zero)
return;
var positions = mesh.positionsInternal;
if (System.Math.Abs(currentSize.size.x) < 0.001f)
scale.x = 0;
if (System.Math.Abs(currentSize.size.y) < 0.001f)
scale.y = 0;
if (System.Math.Abs(currentSize.size.z) < 0.001f)
scale.z = 0;
for (int i = 0, c = mesh.vertexCount; i < c; i++)
{
positions[i] -= currentSize.center;
positions[i].Scale(scale);
positions[i] += currentSize.center;
}
mesh.Rebuild();
}
internal static string SanityCheck(ProBuilderMesh mesh)
{
return SanityCheck(mesh.GetVertices());
}
///
/// Check mesh for invalid properties.
///
///
/// Returns true if mesh is valid, false if a problem was found.
internal static string SanityCheck(Mesh mesh)
{
return SanityCheck(mesh.GetVertices());
}
///
/// Check mesh for invalid properties.
///
/// Returns true if mesh is valid, false if a problem was found.
internal static string SanityCheck(IList vertices)
{
var sb = new StringBuilder();
for (int i = 0, c = vertices.Count; i < c; i++)
{
var vertex = vertices[i];
if (Math.IsNumber(vertex.position)
&& Math.IsNumber(vertex.color)
&& Math.IsNumber(vertex.uv0)
&& Math.IsNumber(vertex.normal)
&& Math.IsNumber(vertex.tangent)
&& Math.IsNumber(vertex.uv2)
&& Math.IsNumber(vertex.uv3)
&& Math.IsNumber(vertex.uv4))
continue;
sb.AppendFormat("vertex {0} contains invalid values:\n{1}\n\n", i, vertex.ToString());
}
return sb.ToString();
}
internal static bool IsUsedInParticleSystem(ProBuilderMesh pbmesh)
{
#if USING_PARTICLE_SYSTEM
ParticleSystem pSys;
if(pbmesh.TryGetComponent(out pSys))
{
var shapeModule = pSys.shape;
if(shapeModule.meshRenderer == pbmesh.renderer)
{
shapeModule.meshRenderer = null;
return true;
}
}
#endif
return false;
}
internal static void RestoreParticleSystem(ProBuilderMesh pbmesh)
{
#if USING_PARTICLE_SYSTEM
ParticleSystem pSys;
if(pbmesh.TryGetComponent(out pSys))
{
var shapeModule = pSys.shape;
shapeModule.meshRenderer = pbmesh.renderer;
}
#endif
}
internal static Bounds GetBounds(this ProBuilderMesh mesh)
{
if (mesh.mesh != null)
return mesh.mesh.bounds;
return Math.GetBounds(mesh.positionsInternal);
}
}
}