using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.ProBuilder;
namespace UnityEngine.ProBuilder.MeshOperations
{
///
/// Utilities for working with triangle and quad primitives.
///
public static class SurfaceTopology
{
///
/// Convert a selection of faces from n-gons to triangles.
///
/// If a face is successfully converted to triangles, each new triangle is created as a separate face and the original face is deleted.
///
/// The target mesh.
/// The faces to convert from quads to triangles.
/// Any new triangle faces created by breaking faces into individual triangles.
public static Face[] ToTriangles(this ProBuilderMesh mesh, IList faces)
{
if (mesh == null)
throw new System.ArgumentNullException("mesh");
if (faces == null)
throw new System.ArgumentNullException("faces");
List vertices = new List(mesh.GetVertices());
Dictionary lookup = mesh.sharedVertexLookup;
List rebuild = new List();
foreach (Face face in faces)
{
List res = BreakFaceIntoTris(face, vertices, lookup);
rebuild.AddRange(res);
}
FaceRebuildData.Apply(rebuild, mesh, vertices, null);
mesh.DeleteFaces(faces);
mesh.ToMesh();
return rebuild.Select(x => x.face).ToArray();
}
static List BreakFaceIntoTris(Face face, List vertices, Dictionary lookup)
{
int[] tris = face.indexesInternal;
int triCount = tris.Length;
List rebuild = new List(triCount / 3);
for (int i = 0; i < triCount; i += 3)
{
FaceRebuildData r = new FaceRebuildData();
r.face = new Face(face);
r.face.indexesInternal = new int[] { 0, 1, 2};
r.vertices = new List() {
vertices[tris[i]],
vertices[tris[i + 1]],
vertices[tris[i + 2]]
};
r.sharedIndexes = new List() {
lookup[tris[i]],
lookup[tris[i + 1]],
lookup[tris[i + 2]]
};
rebuild.Add(r);
}
return rebuild;
}
///
/// Attempt to extract the winding order for a face.
///
/// The source mesh.
/// The face to test.
/// The winding order if successfull, unknown if not.
public static WindingOrder GetWindingOrder(this ProBuilderMesh mesh, Face face)
{
Vector2[] p = Projection.PlanarProject(mesh.positionsInternal, face.distinctIndexesInternal);
return GetWindingOrder(p);
}
static WindingOrder GetWindingOrder(IList vertices, IList indexes)
{
if (vertices == null)
throw new ArgumentNullException("vertices");
if (indexes == null)
throw new ArgumentNullException("indexes");
Vector2[] p = Projection.PlanarProject(vertices.Select(x => x.position).ToArray(), indexes);
return GetWindingOrder(p);
}
///
/// Return the winding order of a set of ordered points.
///
/// http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order
/// A path of points in 2d space.
/// The winding order if found, WindingOrder.Unknown if not.
public static WindingOrder GetWindingOrder(IList points)
{
if (points == null)
throw new ArgumentNullException("points");
float sum = 0f;
int len = points.Count;
// http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order
for (int i = 0; i < len; i++)
{
Vector2 a = points[i];
Vector2 b = i < len - 1 ? points[i + 1] : points[0];
sum += ((b.x - a.x) * (b.y + a.y));
}
return sum == 0f ? WindingOrder.Unknown : (sum > 0f ? WindingOrder.Clockwise : WindingOrder.CounterClockwise);
}
///
/// Reverses the orientation of the middle edge in a quad.
/// | / |
/// . |____\| |/____|
/// ```
/// ]]>
///
/// The mesh that face belongs to.
/// The target face.
/// True if successful, false if not. Operation will fail if face does not contain two triangles with exactly 2 shared vertices.
public static bool FlipEdge(this ProBuilderMesh mesh, Face face)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
if (face == null)
throw new ArgumentNullException("face");
int[] indexes = face.indexesInternal;
if (indexes.Length != 6)
return false;
int[] mode = ArrayUtility.Fill(1, indexes.Length);
for (int x = 0; x < indexes.Length - 1; x++)
{
for (int y = x + 1; y < indexes.Length; y++)
{
if (indexes[x] == indexes[y])
{
mode[x]++;
mode[y]++;
}
}
}
if (mode[0] + mode[1] + mode[2] != 5 ||
mode[3] + mode[4] + mode[5] != 5)
return false;
int i0 = indexes[mode[0] == 1 ? 0 : mode[1] == 1 ? 1 : 2];
int i1 = indexes[mode[3] == 1 ? 3 : mode[4] == 1 ? 4 : 5];
int used = -1;
if (mode[0] == 2)
{
used = indexes[0];
indexes[0] = i1;
}
else if (mode[1] == 2)
{
used = indexes[1];
indexes[1] = i1;
}
else if (mode[2] == 2)
{
used = indexes[2];
indexes[2] = i1;
}
if (mode[3] == 2 && indexes[3] != used)
indexes[3] = i0;
else if (mode[4] == 2 && indexes[4] != used)
indexes[4] = i0;
else if (mode[5] == 2 && indexes[5] != used)
indexes[5] = i0;
face.InvalidateCache();
return true;
}
///
/// Ensure that all adjacent face normals are pointing in a uniform direction. This function supports multiple islands of connected faces, but it may not unify each island the same way.
///
/// The mesh that the faces belong to.
/// The faces to make uniform.
/// The state of the action.
public static ActionResult ConformNormals(this ProBuilderMesh mesh, IEnumerable faces)
{
List wings = WingedEdge.GetWingedEdges(mesh, faces);
HashSet used = new HashSet();
int count = 0;
// this loop adds support for multiple islands of grouped selections
for (int i = 0; i < wings.Count; i++)
{
if (used.Contains(wings[i].face))
continue;
Dictionary flags = new Dictionary();
GetWindingFlags(wings[i], true, flags);
int flip = 0;
foreach (var kvp in flags)
flip += kvp.Value ? 1 : -1;
bool direction = flip > 0;
foreach (var kvp in flags)
{
if (direction != kvp.Value)
{
count++;
kvp.Key.Reverse();
}
}
used.UnionWith(flags.Keys);
}
if (count > 0)
return new ActionResult(ActionResult.Status.Success, count > 1 ? string.Format("Flipped {0} faces", count) : "Flipped 1 face");
else
return new ActionResult(ActionResult.Status.NoChange, "Faces Uniform");
}
static void GetWindingFlags(WingedEdge edge, bool flag, Dictionary flags)
{
flags.Add(edge.face, flag);
WingedEdge next = edge;
do
{
WingedEdge opp = next.opposite;
if (opp != null && !flags.ContainsKey(opp.face))
{
Edge cea = GetCommonEdgeInWindingOrder(next);
Edge ceb = GetCommonEdgeInWindingOrder(opp);
GetWindingFlags(opp, cea.a == ceb.a ? !flag : flag, flags);
}
next = next.next;
}
while (next != edge);
}
///
/// Ensure the opposite face to source matches the winding order.
///
///
///
internal static ActionResult ConformOppositeNormal(WingedEdge source)
{
if (source == null || source.opposite == null)
return new ActionResult(ActionResult.Status.Failure, "Source edge does not share an edge with another face.");
Edge cea = GetCommonEdgeInWindingOrder(source);
Edge ceb = GetCommonEdgeInWindingOrder(source.opposite);
if (cea.a == ceb.a)
{
source.opposite.face.Reverse();
return new ActionResult(ActionResult.Status.Success, "Reversed target face winding order.");
}
return new ActionResult(ActionResult.Status.NoChange, "Faces already unified.");
}
///
/// Iterate a face and return a new common edge where the edge indexes are true to the triangle winding order.
///
///
///
static Edge GetCommonEdgeInWindingOrder(WingedEdge wing)
{
int[] indexes = wing.face.indexesInternal;
int len = indexes.Length;
for (int i = 0; i < len; i += 3)
{
Edge e = wing.edge.local;
int a = indexes[i], b = indexes[i + 1], c = indexes[i + 2];
if (e.a == a && e.b == b)
return wing.edge.common;
else if (e.a == b && e.b == a)
return new Edge(wing.edge.common.b, wing.edge.common.a);
else if (e.a == b && e.b == c)
return wing.edge.common;
else if (e.a == c && e.b == b)
return new Edge(wing.edge.common.b, wing.edge.common.a);
else if (e.a == c && e.b == a)
return wing.edge.common;
else if (e.a == a && e.b == c)
return new Edge(wing.edge.common.b, wing.edge.common.a);
}
return Edge.Empty;
}
///
/// Match a target face to the source face. Faces must be adjacent.
///
///
///
///
internal static void MatchNormal(Face source, Face target, Dictionary lookup)
{
List sourceEdges = EdgeLookup.GetEdgeLookup(source.edgesInternal, lookup).ToList();
List targetEdges = EdgeLookup.GetEdgeLookup(target.edgesInternal, lookup).ToList();
bool superBreak = false;
Edge src, tar;
for (int i = 0; !superBreak && i < sourceEdges.Count; i++)
{
src = sourceEdges[i].common;
for (int n = 0; !superBreak && n < targetEdges.Count; n++)
{
tar = targetEdges[n].common;
if (src.Equals(tar))
{
if (src.a == tar.a)
target.Reverse();
superBreak = true;
}
}
}
}
}
}