using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.ProBuilder;
namespace UnityEngine.ProBuilder.MeshOperations
{
///
/// A collection of settings used when importing models to the ProBuilderMesh component.
///
[Serializable]
public sealed class MeshImportSettings
{
[SerializeField]
bool m_Quads = true;
[SerializeField]
bool m_Smoothing = true;
[SerializeField]
float m_SmoothingThreshold = 1f;
///
/// Try to quadrangilize triangle meshes.
///
public bool quads
{
get { return m_Quads; }
set { m_Quads = value; }
}
// Allow ngons when importing meshes. @todo
// public bool ngons = false;
///
/// Generate smoothing groups based on mesh normals.
///
public bool smoothing
{
get { return m_Smoothing; }
set { m_Smoothing = value; }
}
///
/// Degree of difference between face normals to allow when determining smoothing groups.
///
public float smoothingAngle
{
get { return m_SmoothingThreshold; }
set { m_SmoothingThreshold = value; }
}
public override string ToString()
{
return string.Format("quads: {0}\nsmoothing: {1}\nthreshold: {2}",
quads,
smoothing,
smoothingAngle);
}
}
///
/// Responsible for importing UnityEngine.Mesh data to a ProBuilderMesh component.
///
public sealed class MeshImporter
{
static readonly MeshImportSettings k_DefaultImportSettings = new MeshImportSettings()
{
quads = true,
smoothing = true,
smoothingAngle = 1f
};
ProBuilderMesh m_Mesh;
Vertex[] m_Vertices;
///
/// Create a new MeshImporter instance.
///
/// The ProBuilderMesh component that will be initialized with the imported mesh attributes.
public MeshImporter(ProBuilderMesh target)
{
m_Mesh = target;
}
///
/// Import mesh data from a GameObject's MeshFilter.sharedMesh and MeshRenderer.sharedMaterials.
///
/// The GameObject to search for MeshFilter and MeshRenderer data.
/// Optional settings parameter defines import customization properties.
/// True if the mesh data was successfully translated to the ProBuilderMesh target, false if something went wrong.
internal bool Import(GameObject gameObject, MeshImportSettings importSettings = null)
{
if (gameObject == null)
throw new ArgumentNullException("gameObject");
MeshFilter meshFilter = gameObject.GetComponent();
MeshRenderer mr = gameObject.GetComponent();
if (meshFilter == null || meshFilter.sharedMesh == null)
{
Log.Error("GameObject does not contain a valid MeshFilter or sharedMesh.");
return false;
}
try
{
Import(meshFilter.sharedMesh, mr ? mr.sharedMaterials : null, importSettings);
}
catch (Exception e)
{
Log.Warning(e.ToString());
return false;
}
return true;
}
///
/// Import mesh data from a GameObject's MeshFilter.sharedMesh and MeshRenderer.sharedMaterials.
///
/// The UnityEngine.Mesh to extract attributes from.
/// The materials array corresponding to the originalMesh submeshes.
/// Optional settings parameter defines import customization properties.
/// Import only supports triangle and quad mesh topologies.
public void Import(Mesh originalMesh, Material[] materials = null, MeshImportSettings importSettings = null)
{
if (originalMesh == null)
throw new ArgumentNullException("originalMesh");
if (importSettings == null)
importSettings = k_DefaultImportSettings;
// When importing the mesh is always split into triangles with no vertices shared
// between faces. In a later step co-incident vertices are collapsed (eg, before
// leaving the Import function).
Vertex[] sourceVertices = originalMesh.GetVertices();
List splitVertices = new List();
List faces = new List();
// Fill in Faces array with just the position indexes. In the next step we'll
// figure out smoothing groups & merging
int vertexIndex = 0;
int materialCount = materials != null ? materials.Length : 0;
for (int submeshIndex = 0; submeshIndex < originalMesh.subMeshCount; submeshIndex++)
{
switch (originalMesh.GetTopology(submeshIndex))
{
case UnityEngine.MeshTopology.Triangles:
{
int[] indexes = originalMesh.GetIndices(submeshIndex);
for (int tri = 0; tri < indexes.Length; tri += 3)
{
faces.Add(new Face(
new int[] { vertexIndex, vertexIndex + 1, vertexIndex + 2 },
Math.Clamp(submeshIndex, 0, materialCount - 1),
AutoUnwrapSettings.tile,
Smoothing.smoothingGroupNone,
-1,
-1,
true));
splitVertices.Add(sourceVertices[indexes[tri]]);
splitVertices.Add(sourceVertices[indexes[tri + 1]]);
splitVertices.Add(sourceVertices[indexes[tri + 2]]);
vertexIndex += 3;
}
}
break;
case UnityEngine.MeshTopology.Quads:
{
int[] indexes = originalMesh.GetIndices(submeshIndex);
for (int quad = 0; quad < indexes.Length; quad += 4)
{
faces.Add(new Face(new int[] {
vertexIndex , vertexIndex + 1, vertexIndex + 2,
vertexIndex + 2, vertexIndex + 3, vertexIndex + 0
},
Math.Clamp(submeshIndex, 0, materialCount - 1),
AutoUnwrapSettings.tile,
Smoothing.smoothingGroupNone,
-1,
-1,
true));
splitVertices.Add(sourceVertices[indexes[quad]]);
splitVertices.Add(sourceVertices[indexes[quad + 1]]);
splitVertices.Add(sourceVertices[indexes[quad + 2]]);
splitVertices.Add(sourceVertices[indexes[quad + 3]]);
vertexIndex += 4;
}
}
break;
default:
throw new NotSupportedException("ProBuilder only supports importing triangle and quad meshes.");
}
}
m_Vertices = splitVertices.ToArray();
m_Mesh.Clear();
m_Mesh.SetVertices(m_Vertices);
m_Mesh.faces = faces;
m_Mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(m_Mesh.positionsInternal);
m_Mesh.sharedTextures = new SharedVertex[0];
HashSet processed = new HashSet();
if (importSettings.quads)
{
List wings = WingedEdge.GetWingedEdges(m_Mesh, m_Mesh.facesInternal, true);
// build a lookup of the strength of edge connections between triangle faces
Dictionary connections = new Dictionary();
for (int i = 0; i < wings.Count; i++)
{
using (var it = new WingedEdgeEnumerator(wings[i]))
{
while (it.MoveNext())
{
var border = it.Current;
if (border.opposite != null && !connections.ContainsKey(border.edge))
{
float score = GetQuadScore(border, border.opposite);
connections.Add(border.edge, score);
}
}
}
}
List> quads = new List>();
// move through each face and find it's best quad neighbor
foreach (WingedEdge face in wings)
{
if (!processed.Add(face.face))
continue;
float bestScore = 0f;
Face buddy = null;
using (var it = new WingedEdgeEnumerator(face))
{
while (it.MoveNext())
{
var border = it.Current;
if (border.opposite != null && processed.Contains(border.opposite.face))
continue;
float borderScore;
// only add it if the opposite face's best score is also this face
if (connections.TryGetValue(border.edge, out borderScore) &&
borderScore > bestScore &&
face.face == GetBestQuadConnection(border.opposite, connections))
{
bestScore = borderScore;
buddy = border.opposite.face;
}
}
}
if (buddy != null)
{
processed.Add(buddy);
quads.Add(new SimpleTuple(face.face, buddy));
}
}
// don't collapse coincident vertices if smoothing is enabled, we need the original normals intact
MergeElements.MergePairs(m_Mesh, quads, !importSettings.smoothing);
}
if (importSettings.smoothing)
{
Smoothing.ApplySmoothingGroups(m_Mesh, m_Mesh.facesInternal, importSettings.smoothingAngle, m_Vertices.Select(x => x.normal).ToArray());
// After smoothing has been applied go back and weld coincident vertices created by MergePairs.
MergeElements.CollapseCoincidentVertices(m_Mesh, m_Mesh.facesInternal);
}
}
Face GetBestQuadConnection(WingedEdge wing, Dictionary connections)
{
float score = 0f;
Face face = null;
using (var it = new WingedEdgeEnumerator(wing))
{
while (it.MoveNext())
{
var border = it.Current;
float s = 0f;
if (connections.TryGetValue(border.edge, out s) && s > score)
{
score = connections[border.edge];
face = border.opposite.face;
}
}
}
return face;
}
/**
* Get a weighted value for the quality of a quad composed of two triangles. 0 is terrible, 1 is perfect.
* normalThreshold will discard any quads where the dot product of their normals is less than the threshold.
* @todo Abstract the quad detection to a separate class so it can be applied to pb_Objects.
*/
float GetQuadScore(WingedEdge left, WingedEdge right, float normalThreshold = .9f)
{
int[] quad = WingedEdge.MakeQuad(left, right);
if (quad == null)
return 0f;
// first check normals
Vector3 leftNormal = Math.Normal(m_Vertices[quad[0]].position, m_Vertices[quad[1]].position, m_Vertices[quad[2]].position);
Vector3 rightNormal = Math.Normal(m_Vertices[quad[2]].position, m_Vertices[quad[3]].position, m_Vertices[quad[0]].position);
float score = Vector3.Dot(leftNormal, rightNormal);
if (score < normalThreshold)
return 0f;
// next is right-angle-ness check
Vector3 a = (m_Vertices[quad[1]].position - m_Vertices[quad[0]].position);
Vector3 b = (m_Vertices[quad[2]].position - m_Vertices[quad[1]].position);
Vector3 c = (m_Vertices[quad[3]].position - m_Vertices[quad[2]].position);
Vector3 d = (m_Vertices[quad[0]].position - m_Vertices[quad[3]].position);
a.Normalize();
b.Normalize();
c.Normalize();
d.Normalize();
float da = Mathf.Abs(Vector3.Dot(a, b));
float db = Mathf.Abs(Vector3.Dot(b, c));
float dc = Mathf.Abs(Vector3.Dot(c, d));
float dd = Mathf.Abs(Vector3.Dot(d, a));
score += 1f - ((da + db + dc + dd) * .25f);
// and how close to parallel the opposite sides area
score += Mathf.Abs(Vector3.Dot(a, c)) * .5f;
score += Mathf.Abs(Vector3.Dot(b, d)) * .5f;
// the three tests each contribute 1
return score * .33f;
}
}
}