using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using System;
namespace UnityEngine.ProBuilder.MeshOperations
{
///
/// Store face rebuild data with indexes to mark which vertices are new.
///
sealed class ConnectFaceRebuildData
{
public FaceRebuildData faceRebuildData;
public List newVertexIndexes;
public ConnectFaceRebuildData(FaceRebuildData faceRebuildData, List newVertexIndexes)
{
this.faceRebuildData = faceRebuildData;
this.newVertexIndexes = newVertexIndexes;
}
}
///
/// Utility class for connecting edges, faces, and vertices.
///
public static class ConnectElements
{
///
/// Insert new edges from the center of each edge on a face to a new vertex in the center of the face.
///
/// Target mesh.
/// The faces to poke.
/// The faces created as a result of inserting new edges.
public static Face[] Connect(this ProBuilderMesh mesh, IEnumerable faces)
{
var split = MeshValidation.EnsureFacesAreComposedOfContiguousTriangles(mesh, faces);
HashSet mask = new HashSet(faces);
if (split.Count > 0)
{
foreach (var face in split)
mask.Add(face);
}
IEnumerable edges = mask.SelectMany(x => x.edgesInternal);
Edge[] empty;
Face[] res;
Connect(mesh, edges, out res, out empty, true, false, mask);
return res;
}
///
/// Insert new edges connecting a set of edges. If a face contains more than 2 edges to be connected, a new vertex is inserted at the center of the face and each edge is connected to the center point.
///
/// The target mesh.
/// A list of edges to connect.
/// The faces and edges created as a result of inserting new edges.
public static SimpleTuple Connect(this ProBuilderMesh mesh, IEnumerable edges)
{
Edge[] empty;
Face[] faces;
Connect(mesh, edges, out faces, out empty, true, true);
return new SimpleTuple(faces, empty);
}
///
/// Inserts edges connecting a list of indexes.
///
/// The target mesh.
/// A list of indexes (corresponding to the @"UnityEngine.ProBuilder.ProBuilderMesh.positions" array) to connect with new edges.
/// Because this function may modify the ordering of the positions array, a new array containing the equivalent values of the passed connected indexes is returned.
public static int[] Connect(this ProBuilderMesh mesh, IList indexes)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
if (indexes == null)
throw new ArgumentNullException("indexes");
int sharedIndexOffset = mesh.sharedVerticesInternal.Length;
Dictionary lookup = mesh.sharedVertexLookup;
HashSet distinct = new HashSet(indexes.Select(x => lookup[x]));
HashSet affected = new HashSet();
foreach (int i in distinct)
affected.UnionWith(mesh.sharedVerticesInternal[i].arrayInternal);
Dictionary> splits = new Dictionary>();
List vertices = new List(mesh.GetVertices());
foreach (Face face in mesh.facesInternal)
{
int[] f = face.distinctIndexesInternal;
for (int i = 0; i < f.Length; i++)
{
if (affected.Contains(f[i]))
splits.AddOrAppend(face, f[i]);
}
}
List appendFaces = new List();
List successfulSplits = new List();
HashSet usedTextureGroups = new HashSet(mesh.facesInternal.Select(x => x.textureGroup));
int newTextureGroupIndex = 1;
foreach (KeyValuePair> split in splits)
{
Face face = split.Key;
List res = split.Value.Count == 2 ?
ConnectIndexesPerFace(face, split.Value[0], split.Value[1], vertices, lookup) :
ConnectIndexesPerFace(face, split.Value, vertices, lookup, sharedIndexOffset++);
if (res == null)
continue;
if (face.textureGroup < 0)
{
while (usedTextureGroups.Contains(newTextureGroupIndex))
newTextureGroupIndex++;
usedTextureGroups.Add(newTextureGroupIndex);
}
foreach (ConnectFaceRebuildData c in res)
{
c.faceRebuildData.face.textureGroup = face.textureGroup < 0 ? newTextureGroupIndex : face.textureGroup;
c.faceRebuildData.face.uv = new AutoUnwrapSettings(face.uv);
c.faceRebuildData.face.smoothingGroup = face.smoothingGroup;
c.faceRebuildData.face.manualUV = face.manualUV;
c.faceRebuildData.face.submeshIndex = face.submeshIndex;
}
successfulSplits.Add(face);
appendFaces.AddRange(res);
}
FaceRebuildData.Apply(appendFaces.Select(x => x.faceRebuildData), mesh, vertices, null);
int removedVertexCount = mesh.DeleteFaces(successfulSplits).Length;
lookup = mesh.sharedVertexLookup;
HashSet newVertexIndexes = new HashSet();
for (int i = 0; i < appendFaces.Count; i++)
for (int n = 0; n < appendFaces[i].newVertexIndexes.Count; n++)
newVertexIndexes.Add(lookup[appendFaces[i].newVertexIndexes[n] + (appendFaces[i].faceRebuildData.Offset() - removedVertexCount)]);
mesh.ToMesh();
return newVertexIndexes.Select(x => mesh.sharedVerticesInternal[x][0]).ToArray();
}
///
/// Inserts new edges connecting the passed edges, optionally restricting new edge insertion to faces in faceMask.
///
///
///
///
///
///
///
///
///
internal static ActionResult Connect(
this ProBuilderMesh mesh,
IEnumerable edges,
out Face[] addedFaces,
out Edge[] connections,
bool returnFaces = false,
bool returnEdges = false,
HashSet faceMask = null)
{
Dictionary lookup = mesh.sharedVertexLookup;
Dictionary lookupUV = mesh.sharedTextureLookup;
HashSet distinctEdges = new HashSet(EdgeLookup.GetEdgeLookup(edges, lookup));
List wings = WingedEdge.GetWingedEdges(mesh);
// map each edge to a face so that we have a list of all touched faces with their to-be-subdivided edges
Dictionary> touched = new Dictionary>();
foreach (WingedEdge wing in wings)
{
if (distinctEdges.Contains(wing.edge))
{
List faceEdges;
if (touched.TryGetValue(wing.face, out faceEdges))
faceEdges.Add(wing);
else
touched.Add(wing.face, new List() { wing });
}
}
Dictionary> affected = new Dictionary>();
// weed out edges that won't actually connect to other edges (if you don't play ya' can't stay)
foreach (KeyValuePair> kvp in touched)
{
if (kvp.Value.Count <= 1)
{
WingedEdge opp = kvp.Value[0].opposite;
if (opp == null)
continue;
List opp_list;
if (!touched.TryGetValue(opp.face, out opp_list))
continue;
if (opp_list.Count <= 1)
continue;
}
affected.Add(kvp.Key, kvp.Value);
}
List vertices = new List(mesh.GetVertices());
List results = new List();
// just the faces that where connected with > 1 edge
List connectedFaces = new List();
HashSet usedTextureGroups = new HashSet(mesh.facesInternal.Select(x => x.textureGroup));
int newTextureGroupIndex = 1;
// do the splits
foreach (KeyValuePair> split in affected)
{
Face face = split.Key;
List targetEdges = split.Value;
int inserts = targetEdges.Count;
Vector3 nrm = Math.Normal(vertices, face.indexesInternal);
if (inserts == 1 || (faceMask != null && !faceMask.Contains(face)))
{
ConnectFaceRebuildData c;
if (InsertVertices(face, targetEdges, vertices, out c))
{
Vector3 fn = Math.Normal(c.faceRebuildData.vertices, c.faceRebuildData.face.indexesInternal);
if (Vector3.Dot(nrm, fn) < 0)
c.faceRebuildData.face.Reverse();
results.Add(c);
}
}
else if (inserts > 1)
{
List res = inserts == 2 ?
ConnectEdgesInFace(face, targetEdges[0], targetEdges[1], vertices) :
ConnectEdgesInFace(face, targetEdges, vertices);
if (face.textureGroup < 0)
{
while (usedTextureGroups.Contains(newTextureGroupIndex))
newTextureGroupIndex++;
usedTextureGroups.Add(newTextureGroupIndex);
}
if (res == null)
{
connections = null;
addedFaces = null;
return new ActionResult(ActionResult.Status.Failure, "Unable to connect faces");
}
else
{
foreach (ConnectFaceRebuildData c in res)
{
connectedFaces.Add(c.faceRebuildData.face);
Vector3 fn = Math.Normal(c.faceRebuildData.vertices,
c.faceRebuildData.face.indexesInternal);
if (Vector3.Dot(nrm, fn) < 0)
c.faceRebuildData.face.Reverse();
c.faceRebuildData.face.textureGroup =
face.textureGroup < 0 ? newTextureGroupIndex : face.textureGroup;
c.faceRebuildData.face.uv = new AutoUnwrapSettings(face.uv);
c.faceRebuildData.face.submeshIndex = face.submeshIndex;
c.faceRebuildData.face.smoothingGroup = face.smoothingGroup;
c.faceRebuildData.face.manualUV = face.manualUV;
}
results.AddRange(res);
}
}
}
FaceRebuildData.Apply(results.Select(x => x.faceRebuildData), mesh, vertices, null);
mesh.sharedTextures = new SharedVertex[0];
int removedVertexCount = mesh.DeleteFaces(affected.Keys).Length;
mesh.sharedVertices = SharedVertex.GetSharedVerticesWithPositions(mesh.positionsInternal);
mesh.ToMesh();
// figure out where the new edges where inserted
if (returnEdges)
{
// offset the newVertexIndexes by whatever the FaceRebuildData did so we can search for the new edges by index
var appended = new HashSet();
for (int n = 0; n < results.Count; n++)
for (int i = 0; i < results[n].newVertexIndexes.Count; i++)
appended.Add((results[n].newVertexIndexes[i] + results[n].faceRebuildData.Offset()) - removedVertexCount);
Dictionary lup = mesh.sharedVertexLookup;
IEnumerable newEdges = results.SelectMany(x => x.faceRebuildData.face.edgesInternal).Where(x => appended.Contains(x.a) && appended.Contains(x.b));
IEnumerable distNewEdges = EdgeLookup.GetEdgeLookup(newEdges, lup);
connections = distNewEdges.Distinct().Select(x => x.local).ToArray();
}
else
{
connections = null;
}
if (returnFaces)
addedFaces = connectedFaces.ToArray();
else
addedFaces = null;
return new ActionResult(ActionResult.Status.Success, string.Format("Connected {0} Edges", results.Count / 2));
}
///
/// Accepts a face and set of edges to split on.
///
///
///
///
///
///
static List ConnectEdgesInFace(
Face face,
WingedEdge a,
WingedEdge b,
List vertices)
{
List perimeter = WingedEdge.SortEdgesByAdjacency(face);
List[] n_vertices = new List[2]
{
new List(),
new List()
};
List[] n_indexes = new List[2]
{
new List(),
new List()
};
int index = 0;
// creates two new polygon perimeter lines by stepping the current face perimeter and inserting new vertices where edges match
for (int i = 0; i < perimeter.Count; i++)
{
n_vertices[index % 2].Add(vertices[perimeter[i].a]);
if (perimeter[i].Equals(a.edge.local) || perimeter[i].Equals(b.edge.local))
{
Vertex mix = Vertex.Mix(vertices[perimeter[i].a], vertices[perimeter[i].b], .5f);
n_indexes[index % 2].Add(n_vertices[index % 2].Count);
n_vertices[index % 2].Add(mix);
index++;
n_indexes[index % 2].Add(n_vertices[index % 2].Count);
n_vertices[index % 2].Add(mix);
}
}
List faces = new List();
for (int i = 0; i < n_vertices.Length; i++)
{
FaceRebuildData f = AppendElements.FaceWithVertices(n_vertices[i], false);
faces.Add(new ConnectFaceRebuildData(f, n_indexes[i]));
}
return faces;
}
///
/// Insert a new vertex at the center of a face and connect the center of all edges to it.
///
///
///
///
///
static List ConnectEdgesInFace(
Face face,
List edges,
List vertices)
{
List perimeter = WingedEdge.SortEdgesByAdjacency(face);
int splitCount = edges.Count;
Vertex centroid = Vertex.Average(vertices, face.distinctIndexesInternal);
List> n_vertices = ArrayUtility.Fill>(x => { return new List(); }, splitCount);
List> n_indexes = ArrayUtility.Fill>(x => { return new List(); }, splitCount);
HashSet edgesToSplit = new HashSet(edges.Select(x => x.edge.local));
int index = 0;
// creates two new polygon perimeter lines by stepping the current face perimeter and inserting new vertices where edges match
for (int i = 0; i < perimeter.Count; i++)
{
n_vertices[index % splitCount].Add(vertices[perimeter[i].a]);
if (edgesToSplit.Contains(perimeter[i]))
{
Vertex mix = Vertex.Mix(vertices[perimeter[i].a], vertices[perimeter[i].b], .5f);
// split current poly line
n_indexes[index].Add(n_vertices[index].Count);
n_vertices[index].Add(mix);
// add the centroid vertex
n_indexes[index].Add(n_vertices[index].Count);
n_vertices[index].Add(centroid);
// advance the poly line index
index = (index + 1) % splitCount;
// then add the edge center vertex and move on
n_vertices[index].Add(mix);
}
}
List faces = new List();
for (int i = 0; i < n_vertices.Count; i++)
{
FaceRebuildData f = AppendElements.FaceWithVertices(n_vertices[i], false);
if (f == null)
{
faces.Clear();
return null;
}
faces.Add(new ConnectFaceRebuildData(f, n_indexes[i]));
}
return faces;
}
static bool InsertVertices(Face face, List edges, List vertices, out ConnectFaceRebuildData data)
{
List perimeter = WingedEdge.SortEdgesByAdjacency(face);
List n_vertices = new List();
List newVertexIndexes = new List();
HashSet affected = new HashSet(edges.Select(x => x.edge.local));
for (int i = 0; i < perimeter.Count; i++)
{
n_vertices.Add(vertices[perimeter[i].a]);
if (affected.Contains(perimeter[i]))
{
newVertexIndexes.Add(n_vertices.Count);
n_vertices.Add(Vertex.Mix(vertices[perimeter[i].a], vertices[perimeter[i].b], .5f));
}
}
FaceRebuildData res = AppendElements.FaceWithVertices(n_vertices, false);
if (res != null)
{
res.face.textureGroup = face.textureGroup;
res.face.uv = new AutoUnwrapSettings(face.uv);
res.face.smoothingGroup = face.smoothingGroup;
res.face.manualUV = face.manualUV;
res.face.submeshIndex = face.submeshIndex;
data = new ConnectFaceRebuildData(res, newVertexIndexes);
return true;
}
data = null;
return false;
}
static List ConnectIndexesPerFace(
Face face,
int a,
int b,
List vertices,
Dictionary lookup)
{
List perimeter = WingedEdge.SortEdgesByAdjacency(face);
List[] n_vertices = new List[] {
new List(),
new List()
};
List[] n_sharedIndexes = new List[] {
new List(),
new List()
};
List[] n_indexes = new List[] {
new List(),
new List()
};
int index = 0;
for (int i = 0; i < perimeter.Count; i++)
{
// trying to connect two vertices that are already connected
if (perimeter[i].Contains(a) && perimeter[i].Contains(b))
return null;
int cur = perimeter[i].a;
n_vertices[index].Add(vertices[cur]);
n_sharedIndexes[index].Add(lookup[cur]);
if (cur == a || cur == b)
{
index = (index + 1) % 2;
n_indexes[index].Add(n_vertices[index].Count);
n_vertices[index].Add(vertices[cur]);
n_sharedIndexes[index].Add(lookup[cur]);
}
}
List faces = new List();
Vector3 nrm = Math.Normal(vertices, face.indexesInternal);
for (int i = 0; i < n_vertices.Length; i++)
{
FaceRebuildData f = AppendElements.FaceWithVertices(n_vertices[i], false);
f.sharedIndexes = n_sharedIndexes[i];
Vector3 fn = Math.Normal(n_vertices[i], f.face.indexesInternal);
if (Vector3.Dot(nrm, fn) < 0)
f.face.Reverse();
faces.Add(new ConnectFaceRebuildData(f, n_indexes[i]));
}
return faces;
}
static List ConnectIndexesPerFace(
Face face,
List indexes,
List vertices,
Dictionary lookup,
int sharedIndexOffset)
{
if (indexes.Count < 3)
return null;
List perimeter = WingedEdge.SortEdgesByAdjacency(face);
int splitCount = indexes.Count;
List> n_vertices = ArrayUtility.Fill>(x => { return new List(); }, splitCount);
List> n_sharedIndexes = ArrayUtility.Fill>(x => { return new List(); }, splitCount);
List> n_indexes = ArrayUtility.Fill>(x => { return new List(); }, splitCount);
Vertex center = Vertex.Average(vertices, indexes);
Vector3 nrm = Math.Normal(vertices, face.indexesInternal);
int index = 0;
for (int i = 0; i < perimeter.Count; i++)
{
int cur = perimeter[i].a;
n_vertices[index].Add(vertices[cur]);
n_sharedIndexes[index].Add(lookup[cur]);
if (indexes.Contains(cur))
{
n_indexes[index].Add(n_vertices[index].Count);
n_vertices[index].Add(center);
n_sharedIndexes[index].Add(sharedIndexOffset);
index = (index + 1) % splitCount;
n_indexes[index].Add(n_vertices[index].Count);
n_vertices[index].Add(vertices[cur]);
n_sharedIndexes[index].Add(lookup[cur]);
}
}
List faces = new List();
for (int i = 0; i < n_vertices.Count; i++)
{
if (n_vertices[i].Count < 3)
continue;
FaceRebuildData f = AppendElements.FaceWithVertices(n_vertices[i], false);
f.sharedIndexes = n_sharedIndexes[i];
Vector3 fn = Math.Normal(n_vertices[i], f.face.indexesInternal);
if (Vector3.Dot(nrm, fn) < 0)
f.face.Reverse();
faces.Add(new ConnectFaceRebuildData(f, n_indexes[i]));
}
return faces;
}
}
}