using System.Collections.Generic;
using UnityEngine;
///
/// Mathematical utility algorithms and related data.
/// Derived from Erik Nordeus's tutorial: https://www.habrador.com/tutorials/math/10-triangulation/
///
namespace BrainFailProductions.PolyFew.AsImpL.MathUtil
{
public static class Triangulation
{
///
/// Triangulate a convex polygon maximizing its triangles areas.
///
/// Vertices of the polygon (assumed to be convex).
/// If true a copy is made to preserve the original list of vertices.
/// List of computed triangles.
public static List TriangulateConvexPolygon(List vertices, bool preserveOriginalVertices = true)
{
List poly = preserveOriginalVertices ? new List(vertices) : vertices;
List triangles = new List();
while (true)
{
if (poly.Count == 3)
{
triangles.Add(new Triangle(poly[0], poly[1], poly[2]));
return triangles;
}
Vertex currVert = FindMaxAreaEarVertex(poly);
triangles.Add(ClipTriangle(currVert, poly));
}
// Trivial triangulation ()
//for (int i = 2; i < convexHullpoints.Count; i++)
//{
// Vertex a = convexHullpoints[0];
// Vertex b = convexHullpoints[i - 1];
// Vertex c = convexHullpoints[i];
// triangles.Add(new Triangle(a, b, c));
//}
//return triangles;
}
///
/// Triangulate a ploygon using ear clipping algorithm.
///
/// Vertices of the polygon in 3D (assumed to be convex or concave).
/// This is basically a 2D algorithm, this is the normal of the plane used to project 3D vertices in 2D.
///
///
/// List of triangles as a result of the triangulation process.
///
/// Derived (adapted/refactored) from:
/// https://www.habrador.com/tutorials/math/10-triangulation/.
/// The points on the polygon should be ordered counter-clockwise.
/// The ear clipping algorithm is O(n*n),
/// another common algorithm is dividing it into trapezoids and it's O(n log n),
/// one can maybe do it in O(n) time but no such version is known.
/// Assumes we have at least 4 points.
///
public static List TriangulateByEarClipping(List origVertices, Vector3 planeNormal, string meshName, bool preserveOriginalVertices = true)
{
//The list with triangles the method returns
List triangles = new List();
List vertices = preserveOriginalVertices ? new List(origVertices) : origVertices;
//Find the next and previous vertex
for (int i = 0; i < vertices.Count; i++)
{
int nextPos = MathUtility.ClampListIndex(i + 1, vertices.Count);
int prevPos = MathUtility.ClampListIndex(i - 1, vertices.Count);
vertices[i].PreviousVertex = vertices[prevPos];
vertices[i].NextVertex = vertices[nextPos];
}
List earVertices = FindEarVertices(vertices, planeNormal);
// Triangulate
while (true)
{
//This means we have just one triangle left
if (vertices.Count == 3)
{
//The final triangle
triangles.Add(new Triangle(vertices[0], vertices[1], vertices[2]));
break;
}
if (earVertices.Count == 0)
{
// try the opposite plane
planeNormal = -planeNormal;
earVertices = FindEarVertices(vertices, planeNormal);
}
if (earVertices.Count == 0)
{
Debug.LogWarningFormat("Cannot find a proper reprojection for mesh '{0}'. Using fallback polygon triangulation.", meshName);
Vertex earVertex = vertices[0];
Triangle newTriangle = ClipTriangle(earVertex, vertices);
triangles.Add(newTriangle);
}
else
{
//Make a triangle of the first ear with maximum area
Vertex earVertex = FindMaxAreaEarVertex(earVertices);//earVertices[0];
Triangle newTriangle = ClipEar(earVertex, earVertices, vertices, planeNormal);
triangles.Add(newTriangle);
}
}
return triangles;
}
public static Triangle ClipTriangle(Vertex vertex, List vertices)
{
Vertex vertexPrev = vertex.PreviousVertex;
Vertex vertexNext = vertex.NextVertex;
// Define the triangle with the given vertex
Triangle newTriangle = new Triangle(vertexPrev, vertex, vertexNext);
// Remove the vertex from the list
vertices.Remove(vertex);
// Update the previous vertex and next vertex (join them with an edge)
vertexPrev.NextVertex = vertexNext;
vertexNext.PreviousVertex = vertexPrev;
return newTriangle;
}
private static Triangle ClipEar(Vertex earVertex, List earVertices, List vertices, Vector3 planeNormal)
{
Vertex earVertexPrev = earVertex.PreviousVertex;
Vertex earVertexNext = earVertex.NextVertex;
Triangle newTriangle = ClipTriangle(earVertex, vertices);
//Remove the vertex from the lists
earVertices.Remove(earVertex);
//...see if we have found a new ear by investigating the two vertices that was part of the ear
if (IsVertexEar(earVertexPrev, vertices, planeNormal))
{
earVertices.Add(earVertexPrev);
}
else
{
earVertices.Remove(earVertexPrev);
}
if (IsVertexEar(earVertexNext, vertices, planeNormal))
{
earVertices.Add(earVertexNext);
}
else
{
earVertices.Remove(earVertexNext);
}
return newTriangle;
}
///
/// Find the vertex of the ear with the maximum area.
///
/// List of ear vertices
/// The vertex of the ear with the maximum area.
private static Vertex FindMaxAreaEarVertex(List earVertices)
{
Vertex maxEarVertex = earVertices[0];
foreach (Vertex v in earVertices)
{
if (v.TriangleArea < maxEarVertex.TriangleArea)
{
maxEarVertex = v;
}
}
return maxEarVertex;
}
private static List FindEarVertices(List vertices, Vector3 planeNormal)
{
//Have to find the ears after we have found if the vertex is reflex or convex
List earVertices = new List();
for (int i = 0; i < vertices.Count; i++)
{
if (IsVertexEar(vertices[i], vertices, planeNormal))
{
earVertices.Add(vertices[i]);
}
}
return earVertices;
}
// Check if a vertex if reflex (concave)
private static bool IsVertexReflex(Vertex v, Vector3 vNormal)
{
//This is a reflex vertex if its triangle is oriented clockwise
Vector2 a = v.PreviousVertex.GetPosOnPlane(vNormal);
Vector2 b = v.GetPosOnPlane(vNormal);
Vector2 c = v.NextVertex.GetPosOnPlane(vNormal);
return !MathUtility.IsTriangleOrientedClockwise(a, b, c);
}
//Check if a vertex is an ear
private static bool IsVertexEar(Vertex v, List vertices, Vector3 planeNormal)
{
//A reflex vertex can't be an ear!
if (IsVertexReflex(v, planeNormal))
{
return false;
}
//This triangle to check point in triangle
Vector2 a = v.PreviousVertex.GetPosOnPlane(planeNormal);
Vector2 b = v.GetPosOnPlane(planeNormal);
Vector2 c = v.NextVertex.GetPosOnPlane(planeNormal);
bool hasPointInside = false;
for (int i = 0; i < vertices.Count; i++)
{
//We only need to check if a reflex vertex is inside of the triangle
if (v != vertices[i] && v.PreviousVertex != vertices[i] && v.NextVertex != vertices[i] && IsVertexReflex(vertices[i], planeNormal))
{
Vector2 p = vertices[i].GetPosOnPlane(planeNormal);
//This means inside and not on the hull
if (MathUtility.IsPointInTriangle(a, b, c, p))
{
hasPointInside = true;
break;
}
}
}
if (!hasPointInside)
{
return true;
}
return false;
}
}
}