/// <summary> /// Determines if a non complex polygon is oriented clockwise (CW) or counter-clockwise (CCW). /// Works for convex as well as concave polygons. /// </summary> /// <param name="vertices">Ordered vertices defining th polygon</param> /// <returns>Polygon oriented CW (true) or CCW (false)</returns> /// /// <exception cref="ArgumentException">If fewer than 3 vertices are provided.</exception> public static bool PolygonOrientedClockwise(List <Vertex> vertices) { if (vertices.Count < 3) { throw new ArgumentException($"A polygon needs at least 3 vertices. Vertices provided: {vertices.Count}"); } //https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order var sum = 0f; var vertexCount = vertices.Count; //only get count once for (var i = 0; i < vertexCount; i++) { var indexOfNextVertex = CollectionsHelper.WrapIndex(i + 1, vertexCount); var x1 = vertices[i].Position.x; var y1 = vertices[i].Position.y; var x2 = vertices[indexOfNextVertex].Position.x; var y2 = vertices[indexOfNextVertex].Position.y; sum += (x2 - x1) * (y2 + y1); } // sum > 0 -> clockwise // sum = 0 -> Positive and negative areas cancel out, as in a figure-eight, probably not intended if that ever happens. No idea if this would still work; log warning // sum < 0 -> counter-clockwise if (Math.Abs(sum) < 0.000010f) { Debug.LogWarning("Sum of all positive and negative areas of polygon cancel out, something's probably wrong with your polygon."); } return(sum > 0); }
/// <summary> /// Triangulates a convex or concave polygon. /// The points on the polygon should be ordered counter-clockwise. /// This algorithm is called ear clipping and it's O(n*n) Another common algorithm is dividing it into trapezoids and it's O(n log n). /// </summary> /// <param name="vertices">Ordered list of vertices making up the polygon. Can be oriented CW as well as CCW.</param> /// <returns></returns> /// <exception cref="ArgumentException">If fewer than 3 vertices are provided.</exception> public static List <Triangle> TriangulateConcaveOrConvexPolygon(List <Vertex> vertices) { if (vertices.Count < 3) { throw new ArgumentException($"A polygon needs at least 3 vertices. Vertices provided: {vertices.Count}"); } //Rest of functions needs orientation to be ccw. Change orientation if it's cw if (GeometryHelper.PolygonOrientedClockwise(vertices)) { vertices.Reverse(); } var triangles = new List <Triangle>(); //If we just have three points, we can just return a single triangle if (vertices.Count == 3) { triangles.Add(new Triangle(vertices[0], vertices[2], vertices[1])); //vertices are expected to be in ccw order but Unity draw order for triangles in meshes is cw, hence 0,2,1 and not 0,1,2 return(triangles); } //Step 1. Set the next and prev vertex for every vertex //Find the next and previous vertex for (var i = 0; i < vertices.Count; i++) { var nextPos = CollectionsHelper.WrapIndex(i + 1, vertices.Count); var prevPos = CollectionsHelper.WrapIndex(i - 1, vertices.Count); vertices[i].PreviousVertex = vertices[prevPos]; vertices[i].NextVertex = vertices[nextPos]; } //Step 2. Find the reflex (concave) and convex vertices, and ear vertices foreach (var v in vertices) { SetIfVertexIsConcaveOrConvex(v); } //Have to find the ears after we have found if the vertex is concave or convex var earVertices = new List <Vertex>(); foreach (var v in vertices) { SetIfVertexIsEar(v, vertices); if (v.IsEar) { earVertices.Add(v); } } //Step 3. 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[0].PreviousVertex, vertices[0].NextVertex)); break; } //Make a triangle of the first ear var earVertex = earVertices[0]; var earVertexPrev = earVertex.PreviousVertex; var earVertexNext = earVertex.NextVertex; var newTriangle = new Triangle(earVertex, earVertexPrev, earVertexNext); triangles.Add(newTriangle); //Remove the vertex from the lists earVertices.Remove(earVertex); vertices.Remove(earVertex); //Update the previous vertex and next vertex so that they are now directly linked together (take current ear vertex out of doubly linked list) earVertexPrev.NextVertex = earVertexNext; earVertexNext.PreviousVertex = earVertexPrev; //...see if we have found a new ear by investigating the two vertices that was part of the ear and add them to the list of ears, if that's the case. SetIfVertexIsConcaveOrConvex(earVertexPrev); SetIfVertexIsConcaveOrConvex(earVertexNext); earVertices.Remove(earVertexPrev); earVertices.Remove(earVertexNext); SetIfVertexIsEar(earVertexPrev, vertices); if (earVertexPrev.IsEar) { earVertices.Add(earVertexPrev); } SetIfVertexIsEar(earVertexNext, vertices); if (earVertexNext.IsEar) { earVertices.Add(earVertexNext); } } return(triangles); }