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; } } }