//Is a vertex an ear? private static bool IsVertexEar(LinkedVertex vertex, HashSet <LinkedVertex> reflectVertices) { //Consider the triangle MyVector2 p_prev = vertex.prevLinkedVertex.pos; MyVector2 p = vertex.pos; MyVector2 p_next = vertex.nextLinkedVertex.pos; Triangle2 t = new Triangle2(p_prev, p, p_next); //If any of the other vertices is within this triangle, then this vertex is not an ear //We only need to check the reflect vertices foreach (LinkedVertex otherVertex in reflectVertices) { MyVector2 test_p = otherVertex.pos; //Dont compare with any of the vertices the triangle consist of if (test_p.Equals(p_prev) || test_p.Equals(p) || test_p.Equals(p_next)) { continue; } //If a relect vertex intersects with the triangle, then this vertex is not an ear if (_Intersections.PointTriangle(t, test_p, includeBorder: true)) { return(false); } } //No vertex is intersecting with the triangle, so this vertex must be an ear return(true); }
//Remove the supertriangle private static void RemoveSuperTriangle(Triangle2 superTriangle, HalfEdgeData2 triangulationData) { //The super triangle doesnt exists anymore because we have split it into many new triangles //But we can use its vertices to figure out which new triangles (or faces belonging to the triangle) //we should delete HashSet <HalfEdgeFace2> triangleFacesToDelete = new HashSet <HalfEdgeFace2>(); //Loop through all vertices belongin to the triangulation foreach (HalfEdgeVertex2 v in triangulationData.vertices) { //If the face attached to this vertex already exists in the list of faces we want to delete //Then dont add it again if (triangleFacesToDelete.Contains(v.edge.face)) { continue; } MyVector2 v1 = v.position; //Is this vertex in the triangulation a vertex in the super triangle? if (v1.Equals(superTriangle.p1) || v1.Equals(superTriangle.p2) || v1.Equals(superTriangle.p3)) { triangleFacesToDelete.Add(v.edge.face); } } //Debug.Log("Triangles to delete: " + trianglesToDelete.Count); //Delete the new triangles with vertices attached to the super triangle foreach (HalfEdgeFace2 f in triangleFacesToDelete) { HalfEdgeHelpMethods.DeleteTriangleFace(f, triangulationData, shouldSetOppositeToNull: true); } }
public static HashSet <Triangle2> TriangulatePoints(HashSet <MyVector2> points, bool addColinearPoints) { //Step 1. Generate the convex hull List <MyVector2> pointsOnConvexHull = _ConvexHull.JarvisMarch_2D(points); //Step 2. Triangulate the convex hull HashSet <Triangle2> triangles = _TriangulatePoints.PointsOnConvexHull(pointsOnConvexHull, addColinearPoints: true); //Step 3. From the points we should add, remove those that are already a part of the triangulation foreach (MyVector2 v in pointsOnConvexHull) { points.Remove(v); } //Step 4. Add the remaining points while splitting the triangles they end up in foreach (MyVector2 currentPoint in points) { //Which triangle is this point in? foreach (Triangle2 t in triangles) { if (_Intersections.PointTriangle(t, currentPoint, includeBorder: true)) { //Split the triangle into three new triangles //We ignore if it ends up on the edge of a triangle //If that happens we should split the edge //But it will most likely not end up exactly on the edge because of floating point precision issues //And we are most likely going to run a Delaunay algorithm on this "bad" triangulation //so it doesn't matter anyway //Create 3 new with correct orientation = clockwise Triangle2 t1 = new Triangle2(t.p1, t.p2, currentPoint); Triangle2 t2 = new Triangle2(t.p2, t.p3, currentPoint); Triangle2 t3 = new Triangle2(t.p3, t.p1, currentPoint); //Remove the old triangle triangles.Remove(t); //Add the new triangles triangles.Add(t1); triangles.Add(t2); triangles.Add(t3); break; } } } return(triangles); }
// // Arrow // public static HashSet <Triangle2> Arrow(MyVector2 p1, MyVector2 p2, float lineWidth, float arrowSize) { HashSet <Triangle2> arrowTriangles = new HashSet <Triangle2>(); //An arrow consists of two parts: the pointy part and the rectangular part //First we have to see if we can fit the parts MyVector2 lineDir = p2 - p1; float lineLength = MyVector2.Magnitude(lineDir); if (lineLength < arrowSize) { Debug.Log("Cant make arrow because line is too short"); return(null); } //Make the arrow tip MyVector2 lineDirNormalized = MyVector2.Normalize(lineDir); MyVector2 arrowBottom = p2 - lineDirNormalized * arrowSize; MyVector2 lineNormal = MyVector2.Normalize(new MyVector2(lineDirNormalized.y, -lineDirNormalized.x)); MyVector2 arrowBottom_R = arrowBottom + lineNormal * arrowSize * 0.5f; MyVector2 arrowBottom_L = arrowBottom - lineNormal * arrowSize * 0.5f; Triangle2 arrowTipTriangle = new Triangle2(p2, arrowBottom_R, arrowBottom_L); arrowTriangles.Add(arrowTipTriangle); //Make the arrow rectangle float halfWidth = lineWidth * 0.5f; MyVector2 p1_T = p1 + lineNormal * halfWidth; MyVector2 p1_B = p1 - lineNormal * halfWidth; MyVector2 p2_T = arrowBottom + lineNormal * halfWidth; MyVector2 p2_B = arrowBottom - lineNormal * halfWidth; HashSet <Triangle2> rectangle = LineSegment(p1_T, p1_B, p2_T, p2_B); foreach (Triangle2 t in rectangle) { arrowTriangles.Add(t); } return(arrowTriangles); }
//Generate two triangles if we know the corners of the rectangle public static HashSet <Triangle2> LineSegment(MyVector2 p1_T, MyVector2 p1_B, MyVector2 p2_T, MyVector2 p2_B) { HashSet <Triangle2> lineTriangles = new HashSet <Triangle2>(); //Create the triangles Triangle2 t1 = new Triangle2(p1_T, p1_B, p2_T); Triangle2 t2 = new Triangle2(p1_B, p2_B, p2_T); lineTriangles.Add(t1); lineTriangles.Add(t2); return(lineTriangles); }
//Optimize a new triangle according to Delaunay triangulation //TODO: This process would have been easier if we had used the HalfEdge data structure private static void OptimizeTriangle(Triangle2 t, HashSet <Triangle2> triangulation) { bool hasOppositeEdge; Triangle2 tOpposite; Edge2 edgeToSwap; FindEdgeInTriangulation(t, triangulation, out hasOppositeEdge, out tOpposite, out edgeToSwap); //If it has no opposite edge we just add triangle to the triangulation because it can't be improved if (!hasOppositeEdge) { triangulation.Add(t); return; } //Debug.Log("Has opposite edge"); //Step 3. Check if we should swap this edge according to Delaunay triangulation rules //a, b, c belongs to the triangle and d is the point on the other triangle //a-c is the edge, which is important so we can flip it, by making the edge b-d MyVector2 a = edgeToSwap.p2; MyVector2 c = edgeToSwap.p1; MyVector2 b = t.GetVertexWhichIsNotPartOfEdge(edgeToSwap); MyVector2 d = tOpposite.GetVertexWhichIsNotPartOfEdge(edgeToSwap); bool shouldFlipEdge = DelaunayMethods.ShouldFlipEdge(a, b, c, d); //bool shouldFlipEdge = DelaunayMethods.ShouldFlipEdgeStable(a, b, c, d); if (shouldFlipEdge) { //First remove the old triangle triangulation.Remove(tOpposite); //Build two new triangles Triangle2 t1 = new Triangle2(a, b, d); Triangle2 t2 = new Triangle2(b, c, d); triangulation.Add(t1); triangulation.Add(t2); //Debug.Log("Flipped edge"); } else { triangulation.Add(t); } }
//Is a point inside, outside, or on the border of a triangle //-1 if outside, 0 if on the border, 1 if inside the triangle //BROKEN use if the point is to the left or right of all edges in the triangle //public static int IsPointInOutsideOnTriangle(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p) //{ // //To avoid floating point precision issues we can add a small value // float epsilon = MathUtility.EPSILON; // float zero = 0f + epsilon; // float one = 1f - epsilon; // //Based on Barycentric coordinates // float denominator = ((p2.y - p3.y) * (p1.x - p3.x) + (p3.x - p2.x) * (p1.y - p3.y)); // float a = ((p2.y - p3.y) * (p.x - p3.x) + (p3.x - p2.x) * (p.y - p3.y)) / denominator; // float b = ((p3.y - p1.y) * (p.x - p3.x) + (p1.x - p3.x) * (p.y - p3.y)) / denominator; // float c = 1 - a - b; // int returnValue = -1; // //The point is on the border, meaning exactly 0 or 1 in a world with no floating precision issues // //The point is on or within the triangle // if (a >= zero && a <= one && b >= zero && b <= one && c >= zero && c <= one) // { // returnValue = 1; // } // return returnValue; //} // // Is a triangle inside a triangle // //Is triangle 1 inside triangle 2? public static bool IsTriangleInsideTriangle(Triangle2 t1, Triangle2 t2) { bool isWithin = false; if ( PointTriangle(t2, t1.p1, false) && PointTriangle(t2, t1.p2, false) && PointTriangle(t2, t1.p3, false)) { isWithin = true; } return(isWithin); }
//Is a point inside, outside, or on the border of a triangle //-1 if outside, 0 if on the border, 1 if inside the triangle //BROKEN use if the point is to the left or right of all edges in the triangle //public static int IsPointInOutsideOnTriangle(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p) //{ // //To avoid floating point precision issues we can add a small value // float epsilon = MathUtility.EPSILON; // float zero = 0f + epsilon; // float one = 1f - epsilon; // //Based on Barycentric coordinates // float denominator = ((p2.y - p3.y) * (p1.x - p3.x) + (p3.x - p2.x) * (p1.y - p3.y)); // float a = ((p2.y - p3.y) * (p.x - p3.x) + (p3.x - p2.x) * (p.y - p3.y)) / denominator; // float b = ((p3.y - p1.y) * (p.x - p3.x) + (p1.x - p3.x) * (p.y - p3.y)) / denominator; // float c = 1 - a - b; // int returnValue = -1; // //The point is on the border, meaning exactly 0 or 1 in a world with no floating precision issues // //The point is on or within the triangle // if (a >= zero && a <= one && b >= zero && b <= one && c >= zero && c <= one) // { // returnValue = 1; // } // return returnValue; //} // // Is a triangle inside a triangle // //Is triangle 1 inside triangle 2? public static bool IsTriangleInsideTriangle(Triangle2 t1, Triangle2 t2) { bool isWithin = false; //Test if each vertex is inside the triangle if ( PointTriangle(t2, t1.p1, false) && PointTriangle(t2, t1.p2, false) && PointTriangle(t2, t1.p3, false)) { isWithin = true; } return(isWithin); }
//HashSet<Triangle2> public static HashSet <Triangle2> UnNormalize(HashSet <Triangle2> normalized, AABB2 aabb, float dMax) { HashSet <Triangle2> unNormalized = new HashSet <Triangle2>(); foreach (Triangle2 t in normalized) { MyVector2 p1 = HelpMethods.UnNormalize(t.p1, aabb, dMax); MyVector2 p2 = HelpMethods.UnNormalize(t.p2, aabb, dMax); MyVector2 p3 = HelpMethods.UnNormalize(t.p3, aabb, dMax); Triangle2 tUnNormalized = new Triangle2(p1, p2, p3); unNormalized.Add(tUnNormalized); } return(unNormalized); }
// // Unity mesh to triangle // //The vertices and triangles are the same as in Unitys built-in Mesh, but in 2d space public static HashSet <Triangle2> MeshToTriangle2(Vector2[] meshVertices, int[] meshTriangles) { HashSet <Triangle2> triangles = new HashSet <Triangle2>(); for (int i = 0; i < meshTriangles.Length; i += 3) { MyVector2 v1 = new MyVector2(meshVertices[meshTriangles[i + 0]].x, meshVertices[meshTriangles[i + 0]].y); MyVector2 v2 = new MyVector2(meshVertices[meshTriangles[i + 1]].x, meshVertices[meshTriangles[i + 1]].y); MyVector2 v3 = new MyVector2(meshVertices[meshTriangles[i + 2]].x, meshVertices[meshTriangles[i + 2]].y); Triangle2 t = new Triangle2(v1, v2, v3); triangles.Add(t); } return(triangles); }
//Remove from points from hashset that are within a triangle private static void RemovePointsWithinTriangle(Triangle2 t, HashSet <MyVector2> points) { HashSet <MyVector2> pointsToRemove = new HashSet <MyVector2>(); foreach (MyVector2 p in points) { if (_Intersections.PointTriangle(t, p, includeBorder: true)) { pointsToRemove.Add(p); } } foreach (MyVector2 p in pointsToRemove) { points.Remove(p); } }
//HashSet<Triangle2> public HashSet <Triangle2> UnNormalize(HashSet <Triangle2> normalized) { HashSet <Triangle2> unNormalized = new HashSet <Triangle2>(); foreach (Triangle2 t in normalized) { MyVector2 p1 = UnNormalize(t.p1); MyVector2 p2 = UnNormalize(t.p2); MyVector2 p3 = UnNormalize(t.p3); Triangle2 tUnNormalized = new Triangle2(p1, p2, p3); unNormalized.Add(tUnNormalized); } return(unNormalized); }
//Find an edge in a triangulation and return the triangle the edge is attached to private static void FindEdgeInTriangulation(Triangle2 tNew, HashSet <Triangle2> triangulation, out bool hasOppositeEdge, out Triangle2 tOpposite, out Edge2 edgeToSwap) { //Step 1. Find the triangle's biggest interior angle and its opposite edge float angleP1 = CalculateInteriorAngle(tNew.p3, tNew.p1, tNew.p2); float angleP2 = CalculateInteriorAngle(tNew.p1, tNew.p2, tNew.p3); float angleP3 = Mathf.PI - (angleP1 + angleP2); MyVector2 vertexWithBiggestInteriorAngle = tNew.p1; if (angleP2 > angleP1) { vertexWithBiggestInteriorAngle = tNew.p2; if (angleP3 > angleP2) { vertexWithBiggestInteriorAngle = tNew.p3; } } else if (angleP3 > angleP1) { vertexWithBiggestInteriorAngle = tNew.p3; } edgeToSwap = tNew.FindOppositeEdgeToVertex(vertexWithBiggestInteriorAngle); //Step 2. Check if this edge exists among the already generated triangles, which means we have a neighbor hasOppositeEdge = false; tOpposite = new Triangle2(); foreach (Triangle2 tTest in triangulation) { if (tTest.IsEdgePartOfTriangle(edgeToSwap)) { hasOppositeEdge = true; tOpposite = tTest; break; } } }
// // Half-edge to triangle if we know the half-edge consists of triangles // public static HashSet <Triangle2> HalfEdge2ToTriangle2(HalfEdgeData2 data) { if (data == null) { return(null); } HashSet <Triangle2> triangles = new HashSet <Triangle2>(); foreach (HalfEdgeFace2 face in data.faces) { MyVector2 p1 = face.edge.v.position; MyVector2 p2 = face.edge.nextEdge.v.position; MyVector2 p3 = face.edge.nextEdge.nextEdge.v.position; Triangle2 t = new Triangle2(p1, p2, p3); triangles.Add(t); } return(triangles); }
// // Is a point inside a triangle? // //From http://totologic.blogspot.se/2014/01/accurate-point-in-triangle-test.html public static bool PointTriangle(Triangle2 t, MyVector2 p, bool includeBorder) { //To avoid floating point precision issues we can add a small value float epsilon = MathUtility.EPSILON; //Based on Barycentric coordinates float denominator = ((t.p2.y - t.p3.y) * (t.p1.x - t.p3.x) + (t.p3.x - t.p2.x) * (t.p1.y - t.p3.y)); float a = ((t.p2.y - t.p3.y) * (p.x - t.p3.x) + (t.p3.x - t.p2.x) * (p.y - t.p3.y)) / denominator; float b = ((t.p3.y - t.p1.y) * (p.x - t.p3.x) + (t.p1.x - t.p3.x) * (p.y - t.p3.y)) / denominator; float c = 1 - a - b; bool isWithinTriangle = false; if (includeBorder) { float zero = 0f - epsilon; float one = 1f + epsilon; //The point is within the triangle or on the border if (a >= zero && a <= one && b >= zero && b <= one && c >= zero && c <= one) { isWithinTriangle = true; } } else { float zero = 0f + epsilon; float one = 1f - epsilon; //The point is within the triangle if (a > zero && a < one && b > zero && b < one && c > zero && c < one) { isWithinTriangle = true; } } return(isWithinTriangle); }
//Help method to split triangle edge private static void SplitTriangleEdge(Triangle2 t, MyVector2 p, HashSet <Triangle2> triangles) { MyVector2 a = t.p1; MyVector2 b = t.p2; MyVector2 c = t.p3; //Which edge should we split? if (Geometry.IsPoint_Left_On_Right_OfVector(a, b, p) == LeftOnRight.On) { Triangle2 t1_new = new Triangle2(a, c, p); Triangle2 t2_new = new Triangle2(b, c, p); triangles.Remove(t); triangles.Add(t1_new); triangles.Add(t2_new); } else if (Geometry.IsPoint_Left_On_Right_OfVector(b, c, p) == LeftOnRight.On) { Triangle2 t1_new = new Triangle2(b, a, p); Triangle2 t2_new = new Triangle2(c, a, p); triangles.Remove(t); triangles.Add(t1_new); triangles.Add(t2_new); } else if (Geometry.IsPoint_Left_On_Right_OfVector(c, a, p) == LeftOnRight.On) { Triangle2 t1_new = new Triangle2(c, b, p); Triangle2 t2_new = new Triangle2(a, b, p); triangles.Remove(t); triangles.Add(t1_new); triangles.Add(t2_new); } }
//Calculate the angle between the vectors if we are going from p1-p2-p3 //Return +180 if "small" or -180 if "large" //public static float CalculateAngleBetweenVectors(MyVector2 p1, MyVector2 p2, MyVector2 p3) //{ // MyVector2 from = p1 - p2; // MyVector2 to = p3 - p2; // float angle = Vector2.SignedAngle(from, to); // return angle; //} //Create a supertriangle that contains all other points //According to the book "Geometric tools for computer graphics" a reasonably sized triangle //is one that contains a circle that contains the axis-aligned bounding rectangle of the points //Is currently not used anywhere because our points are normalized to the range 0-1 //and then we can make a supertriangle by just setting its size to 100 public static Triangle2 GenerateSupertriangle(HashSet <MyVector2> points) { //Step 1. Create a AABB around the points AABB2 aabb = new AABB2(new List <MyVector2>(points)); MyVector2 TL = new MyVector2(aabb.minX, aabb.maxY); MyVector2 TR = new MyVector2(aabb.maxX, aabb.maxY); MyVector2 BR = new MyVector2(aabb.maxX, aabb.minY); //Step2. Find the inscribed circle - the smallest circle that surrounds the AABB MyVector2 circleCenter = (TL + BR) * 0.5f; float circleRadius = MyVector2.Magnitude(circleCenter - TR); //Step 3. Create the smallest triangle that surrounds the circle //All edges of this triangle have the same length float halfSideLenghth = circleRadius / Mathf.Tan(30f * Mathf.Deg2Rad); //The center position of the bottom-edge MyVector2 t_B = new MyVector2(circleCenter.x, circleCenter.y - circleRadius); MyVector2 t_BL = new MyVector2(t_B.x - halfSideLenghth, t_B.y); MyVector2 t_BR = new MyVector2(t_B.x + halfSideLenghth, t_B.y); //The height from the bottom edge to the top vertex float triangleHeight = halfSideLenghth * Mathf.Tan(60f * Mathf.Deg2Rad); MyVector2 t_T = new MyVector2(circleCenter.x, t_B.y + triangleHeight); //The final triangle Triangle2 superTriangle = new Triangle2(t_BR, t_BL, t_T); return(superTriangle); }
// // Alternative 1. Search through all triangles and use point-in-triangle // //Simple but slow public static HalfEdgeFace2 BruteForce(MyVector2 p, HalfEdgeData2 triangulationData) { HalfEdgeFace2 intersectingTriangle = null; foreach (HalfEdgeFace2 f in triangulationData.faces) { //The corners of this triangle MyVector2 v1 = f.edge.v.position; MyVector2 v2 = f.edge.nextEdge.v.position; MyVector2 v3 = f.edge.nextEdge.nextEdge.v.position; Triangle2 t = new Triangle2(v1, v2, v3); //Is the point in this triangle? if (_Intersections.PointTriangle(t, p, true)) { intersectingTriangle = f; break; } } return(intersectingTriangle); }
// // Orient triangles so they have the correct orientation // public static HashSet <Triangle2> OrientTrianglesClockwise(HashSet <Triangle2> triangles) { //Convert to list or we will no be able to update the orientation List <Triangle2> trianglesList = new List <Triangle2>(triangles); for (int i = 0; i < trianglesList.Count; i++) { Triangle2 t = trianglesList[i]; if (!_Geometry.IsTriangleOrientedClockwise(t.p1, t.p2, t.p3)) { t.ChangeOrientation(); trianglesList[i] = t; //Debug.Log("Changed orientation"); } } //Back to hashset triangles = new HashSet <Triangle2>(trianglesList); return(triangles); }
//If you have points on a convex hull, sorted one after each other public static HashSet <Triangle2> GetTriangles(List <MyVector2> points) { List <Triangle2> triangles = new List <Triangle2>(); //This vertex will be a vertex in all triangles (except for some of those that forms colinear points) MyVector2 a = points[0]; List <MyVector2> colinearPoints = new List <MyVector2>(); //And then we just loop through the other edges to make all triangles for (int i = 2; i < points.Count; i++) { MyVector2 b = points[i - 1]; MyVector2 c = points[i]; //If we hadnt had colinear points this would have been the last line //triangles.Add(new Triangle2(a, b, c)); //But we can always make a triangle if the corners of the triangle are co-linear //Then the triangle will be flat. So we have to check if b is one the line between a and c LeftOnRight orientation = Geometry.IsPoint_Left_On_Right_OfVector(a, c, b); if (orientation == LeftOnRight.On) { colinearPoints.Add(b); continue; } else { //First check if we have colinear points that have to be added if (colinearPoints.Count > 0) { //First add the colinear points for (int j = 0; j < colinearPoints.Count; j++) { if (j == 0) { triangles.Add(new Triangle2(a, colinearPoints[j], c)); } else { triangles.Add(new Triangle2(colinearPoints[j - 1], colinearPoints[j], c)); } } //Add the last triangle triangles.Add(new Triangle2(colinearPoints[colinearPoints.Count - 1], b, c)); colinearPoints.Clear(); } else { triangles.Add(new Triangle2(a, b, c)); } } } //We might still have colinear points to add if (colinearPoints.Count > 0) { //Remove the last triangle because it's not valid anymore Triangle2 lastTriangle = triangles[triangles.Count - 1]; triangles.RemoveAt(triangles.Count - 1); //We also have to add the last point on the hull if its colinear MyVector2 lastOnHull = points[points.Count - 1]; MyVector2 lastColinear = colinearPoints[colinearPoints.Count - 1]; LeftOnRight orientation = Geometry.IsPoint_Left_On_Right_OfVector(a, lastColinear, lastOnHull); if (orientation == LeftOnRight.On) { colinearPoints.Add(lastOnHull); } //Add the colinear points //First we have to identify our new a - the point we will anchor all new triangles to //This new a is part of the triangle we removed MyVector2 newA = lastTriangle.p2; //We also add the first point on the hull to colinear points to make it easier to build triangles colinearPoints.Add(a); for (int i = 1; i < colinearPoints.Count; i++) { MyVector2 b = colinearPoints[i - 1]; MyVector2 c = colinearPoints[i]; triangles.Add(new Triangle2(newA, b, c)); //Debug.DrawLine(colinearPoints[i].ToVector3(), Vector3.zero, Color.white, 3f); } } HashSet <Triangle2> finalTriangles = new HashSet <Triangle2>(triangles); return(finalTriangles); }
//We assume an edge is visible from a point if the triangle (formed by travering edges in the convex hull //of the existing triangulation) form a clockwise triangle with the point //https://stackoverflow.com/questions/8898255/check-whether-a-point-is-visible-from-a-face-of-a-2d-convex-hull private static HashSet <Triangle2> TriangulatePointsConvexHull(HashSet <MyVector2> pointsHashset) { HashSet <Triangle2> triangles = new HashSet <Triangle2>(); //The points we will add List <MyVector2> originalPoints = new List <MyVector2>(pointsHashset); //Step 1. Sort the points along x-axis //OrderBy is always soring in ascending order - use OrderByDescending to get in the other order //originalPoints = originalPoints.OrderBy(n => n.x).ThenBy(n => n.y).ToList(); originalPoints = originalPoints.OrderBy(n => n.x).ToList(); //Step 2. Create the first triangle so we can start the algorithm //Assumes the convex hull algorithm sorts in the same way in x and y directions as we do above MyVector2 p1Start = originalPoints[0]; MyVector2 p2Start = originalPoints[1]; MyVector2 p3Start = originalPoints[2]; //Theese points for the first triangle Triangle2 newTriangle = new Triangle2(p1Start, p2Start, p3Start); triangles.Add(newTriangle); //All points that form the triangles HashSet <MyVector2> triangulatedPoints = new HashSet <MyVector2>(); triangulatedPoints.Add(p1Start); triangulatedPoints.Add(p2Start); triangulatedPoints.Add(p3Start); //Step 3. Add the other points one-by-one //Add the other points one by one //Starts at 3 because we have already added 0,1,2 for (int i = 3; i < originalPoints.Count; i++) { MyVector2 pointToAdd = originalPoints[i]; //Find the convex hull of the current triangulation //It generates a counter-clockwise convex hull List <MyVector2> pointsOnHull = _ConvexHull.JarvisMarch(triangulatedPoints); if (pointsOnHull == null) { Debug.Log("No points on hull when triangulating"); continue; } bool couldFormTriangle = false; //Loop through all edges in the convex hull for (int j = 0; j < pointsOnHull.Count; j++) { MyVector2 p1 = pointsOnHull[j]; MyVector2 p2 = pointsOnHull[MathUtility.ClampListIndex(j + 1, pointsOnHull.Count)]; //If this triangle is clockwise, then we can see the edge //so we should create a new triangle with this edge and the point if (Geometry.IsTriangleOrientedClockwise(p1, p2, pointToAdd)) { triangles.Add(new Triangle2(p1, p2, pointToAdd)); couldFormTriangle = true; } } //Add the point to the list of all points in the current triangulation //if the point could form a triangle if (couldFormTriangle) { triangulatedPoints.Add(pointToAdd); } } return(triangles); }
//The hull may still intersect with the edge between the point on the hole and the "visible" point on the hull, //so the point on the hull might not be visible, so we should try to find a better point private static void FindActualVisibleVertexOnHull(EarClippingPolygon hull, EarClippingPolygon hole, MyVector2 intersectionVertex, ref MyVector2 visibleVertex) { //Form a triangle Triangle2 t = new Triangle2(hole.maxX_Vert, intersectionVertex, visibleVertex); //According to litterature, we check if a reflect vertex is within this triangle //If so, one of them is a better visible vertex on the hull List <MyVector2> reflectVertices = FindReflectVertices(hull, hole); //Pick the reflect vertex with the smallest angle //The angle is measure from the point on the hole towards: //- intersection point on the hull //- reflect vertex float minAngle = Mathf.Infinity; //If more than one reflect vertex have the same angle then pick the one closest to the point on the hole float minDistSqr = Mathf.Infinity; foreach (MyVector2 v in reflectVertices) { if (_Intersections.PointTriangle(t, v, includeBorder: true)) { float angle = MathUtility.AngleBetween(intersectionVertex - hole.maxX_Vert, v - hole.maxX_Vert); //Debug.DrawLine(v.ToVector3(1f), hole.maxX_Vert.ToVector3(1f), Color.blue, 2f); //Debug.DrawLine(intersectionVertex.ToVector3(1f), hole.maxX_Vert.ToVector3(1f), Color.black, 2f); //TestAlgorithmsHelpMethods.DebugDrawCircle(v.ToVector3(1f), 0.3f, Color.blue); //Debug.Log(angle * Mathf.Rad2Deg); if (angle < minAngle) { minAngle = angle; visibleVertex = v; //We also need to calculate this in case a future point has the same angle minDistSqr = MyVector2.SqrDistance(v, hole.maxX_Vert); //Debug.Log(minDistanceSqr); //TestAlgorithmsHelpMethods.DebugDrawCircle(v.ToVector3(1f), 0.3f, Color.green); } //If the angle is the same, then pick the vertex which is the closest to the point on the hull else if (Mathf.Abs(angle - minAngle) < MathUtility.EPSILON) { float distSqr = MyVector2.SqrDistance(v, hole.maxX_Vert); //Debug.Log(minDistanceSqr); if (distSqr < minDistSqr) { visibleVertex = v; minDistSqr = distSqr; //TestAlgorithmsHelpMethods.DebugDrawCircle(v.ToVector3(1f), 0.3f, Color.red); //Debug.Log(distSqr); } } } } //Will show how the holes are connected with the hull //Debug.DrawLine(visibleVertex.ToVector3(1f), hole.maxX_Vert.ToVector3(1f), Color.red, 5f); //TestAlgorithmsHelpMethods.DebugDrawCircle(visibleVertex.ToVector3(1f), 0.3f, Color.red); //TestAlgorithmsHelpMethods.DebugDrawCircle(hole.maxX_Vert.ToVector3(1f), 0.3f, Color.red); }
//The grid is always a square //witdh - the width of the entire chunk //cells - the number of cells in one row public static HashSet <Triangle2> GenerateGrid(float width, int cells) { //We cant have a grid with 0 cells if (cells <= 0) { Debug.Log("The grid needs at least one cell"); return(null); } //The width has to be greater than 0 if (width <= 0f) { Debug.Log("The grid needs a positive width"); return(null); } //The number of vertices in one row is always cells + 1 int verticesInOneRow = cells + 1; //The width of one cell float cellWidth = width / (float)cells; //What's the half width of the grid? float halfWidth = width * 0.5f; //Generate vertices List <MyVector2> vertices = new List <MyVector2>(); for (int i = 0; i < verticesInOneRow; i++) { for (int j = 0; j < verticesInOneRow; j++) { MyVector2 vertexPos = new MyVector2(-halfWidth + i * cellWidth, -halfWidth + j * cellWidth); vertices.Add(vertexPos); } } //Generate triangles by using the 1d list as if it was 2d //List<int> triangles = new List<int>(); HashSet <Triangle2> triangles = new HashSet <Triangle2>(); for (int i = 0; i < verticesInOneRow; i++) { for (int j = 0; j < verticesInOneRow; j++) { //We cant build triangles from the first row/column if (i == 0 || j == 0) { continue; } else { //Four vertices int BL_pos = ConvertArrayPos(verticesInOneRow, i - 1, j - 1); int BR_pos = ConvertArrayPos(verticesInOneRow, i - 0, j - 1); int TL_pos = ConvertArrayPos(verticesInOneRow, i - 1, j - 0); int TR_pos = ConvertArrayPos(verticesInOneRow, i - 0, j - 0); MyVector2 BL = vertices[BL_pos]; MyVector2 BR = vertices[BR_pos]; MyVector2 TL = vertices[TL_pos]; MyVector2 TR = vertices[TR_pos]; //Triangle 1 //triangles.Add(TR); //triangles.Add(BL); //triangles.Add(TL); //Triangle 2 //triangles.Add(TR); //triangles.Add(BR); //triangles.Add(BL); Triangle2 t1 = new Triangle2(TR, BL, TL); Triangle2 t2 = new Triangle2(TR, BR, BL); triangles.Add(t1); triangles.Add(t2); } } } //Generate the mesh //Mesh mesh = new Mesh(); //mesh.name = "Grid"; //mesh.vertices = vertices.ToArray(); //mesh.triangles = triangles.ToArray(); //mesh.RecalculateBounds(); //mesh.RecalculateNormals(); return(triangles); }
public static HalfEdgeData2 GenerateTriangulation(HashSet <MyVector2> points, HalfEdgeData2 triangulationData) { //We need more than 1 point to if (points.Count < 2) { Debug.Log("Can make a delaunay with sloan with less than 2 points"); return(null); } //Step 1.Normalize the points to the range(0 - 1), which assumes we have more than 1 point //Is not being done here, we assume the points are already normalized //Step 2. Sort the points into bins to make it faster to find which triangle a point is in //TODO //Step 3. Establish the supertriangle //The report says that the supertriangle should be at (-100, 100) which is way //outside of the points which are in the range(0, 1) //So make sure you have NORMALIZED the points Triangle2 superTriangle = new Triangle2(new MyVector2(-100f, -100f), new MyVector2(100f, -100f), new MyVector2(0f, 100f)); //Create the triangulation data with the only triangle we have HashSet <Triangle2> triangles = new HashSet <Triangle2>(); triangles.Add(superTriangle); //Change to half-edge data structure _TransformBetweenDataStructures.Triangle2ToHalfEdge2(triangles, triangulationData); //Step 4. Loop over each point we want to insert and do Steps 5-7 //These are for display purposes only int missedPoints = 0; int flippedEdges = 0; foreach (MyVector2 p in points) { //Step 5-7 InsertNewPointInTriangulation(p, triangulationData, ref missedPoints, ref flippedEdges); } //Step 8. Delete the vertices belonging to the supertriangle RemoveSuperTriangle(superTriangle, triangulationData); //Step 9.Reset the coordinates to their original values because they are currently in the range (0,1) //Is being done outside of this method //TODO: replace this with StringBuilder string meshDataString = "Delaunay with sloan created a triangulation with: "; meshDataString += "Faces: " + triangulationData.faces.Count; meshDataString += " - Vertices: " + triangulationData.vertices.Count; meshDataString += " - Edges: " + triangulationData.edges.Count; meshDataString += " - Flipped egdes: " + flippedEdges; meshDataString += " - Missed points: " + missedPoints; Debug.Log(meshDataString); return(triangulationData); }
//The points on the hull (vertices) should be ordered counter-clockwise (and no doubles) //The holes should be ordered clockwise (and no doubles) //Optimize triangles means that we will get a better-looking triangulation, which resembles a constrained Delaunay triangulation public static HashSet <Triangle2> Triangulate(List <MyVector2> vertices, List <List <MyVector2> > allHoleVertices = null, bool optimizeTriangles = true) { //Validate the data if (vertices == null || vertices.Count <= 2) { Debug.LogWarning("Can't triangulate with Ear Clipping because too few vertices on the hull"); return(null); } //Step -1. Merge the holes with the points on the hull into one big polygon with invisible edges between the holes and the hull if (allHoleVertices != null && allHoleVertices.Count > 0) { vertices = EarClippingHoleMethods.MergeHolesWithHull(vertices, allHoleVertices); } //TestAlgorithmsHelpMethods.DebugDrawCircle(vertices[29].ToVector3(1f), 0.3f, Color.red); //Step 0. Create a linked list connecting all vertices with each other which will make the calculations easier and faster List <LinkedVertex> verticesLinked = new List <LinkedVertex>(); for (int i = 0; i < vertices.Count; i++) { LinkedVertex v = new LinkedVertex(vertices[i]); verticesLinked.Add(v); } //Link them to each other for (int i = 0; i < verticesLinked.Count; i++) { LinkedVertex v = verticesLinked[i]; v.prevLinkedVertex = verticesLinked[MathUtility.ClampListIndex(i - 1, verticesLinked.Count)]; v.nextLinkedVertex = verticesLinked[MathUtility.ClampListIndex(i + 1, verticesLinked.Count)]; } //Debug.Log("Number of vertices: " + CountLinkedVertices(verticesLinked[0])); //Step 1. Find: //- Convex vertices (interior angle smaller than 180 degrees) //- Reflect vertices (interior angle greater than 180 degrees) so should maybe be called concave vertices? //Interior angle is the angle between two vectors inside the polygon if we move around the polygon counter-clockwise //If they are neither we assume they are reflect (or we will end up with odd triangulations) HashSet <LinkedVertex> convexVerts = new HashSet <LinkedVertex>(); HashSet <LinkedVertex> reflectVerts = new HashSet <LinkedVertex>(); foreach (LinkedVertex v in verticesLinked) { bool isConvex = IsVertexConvex(v); if (isConvex) { convexVerts.Add(v); } else { reflectVerts.Add(v); } } //Step 2. Find the initial ears HashSet <LinkedVertex> earVerts = new HashSet <LinkedVertex>(); //An ear is always a convex vertex foreach (LinkedVertex v in convexVerts) { //And we only need to test the reflect vertices if (IsVertexEar(v, reflectVerts)) { earVerts.Add(v); } } //Debug //DisplayVertices(earVertices); //Step 3. Build the triangles HashSet <Triangle2> triangulation = new HashSet <Triangle2>(); //We know how many triangles we will get (number of vertices - 2) which is true for all simple polygons //This can be used to stop the algorithm int maxTriangles = verticesLinked.Count - 2; //Because we use a while loop, having an extra safety is always good so we dont get stuck in infinite loop int safety = 0; while (true) { //Pick an ear vertex and form a triangle LinkedVertex ear = GetEarVertex(earVerts, optimizeTriangles); if (ear == null) { Debug.Log("Cant find ear"); break; } LinkedVertex v_prev = ear.prevLinkedVertex; LinkedVertex v_next = ear.nextLinkedVertex; Triangle2 t = new Triangle2(ear.pos, v_prev.pos, v_next.pos); //Try to flip this triangle according to Delaunay triangulation if (optimizeTriangles) { OptimizeTriangle(t, triangulation); } else { triangulation.Add(t); } //Check if we have found all triangles //This should also prevent us from getting stuck in an infinite loop if (triangulation.Count >= maxTriangles) { break; } //If we havent found all triangles we have to reconfigure the data structure //Remove the ear we used to build a triangle convexVerts.Remove(ear); earVerts.Remove(ear); //Reconnect the vertices because one vertex has now been removed v_prev.nextLinkedVertex = v_next; v_next.prevLinkedVertex = v_prev; //Reconfigure the adjacent vertices ReconfigureAdjacentVertex(v_prev, convexVerts, reflectVerts, earVerts); ReconfigureAdjacentVertex(v_next, convexVerts, reflectVerts, earVerts); //if (safety > 4) //{ // Debug.Log(earVerts.Count); // Debug.DrawLine(v_next.pos.ToVector3(), Vector3.zero, Color.blue, 3f); // //Debug.Log(IsVertexEar(v_next, reflectVerts)); // Debug.Log(earVerts.Contains(v_next)); // break; //} safety += 1; if (safety > 50000) { Debug.Log("Ear Clipping is stuck in an infinite loop!"); break; } } //Step 4. Improve triangulation //Some triangles may be too sharp, and if you want a nice looking triangle, you should try to swap edges //according to Delaunay triangulation //A report suggests that should be done while createing the triangulation //But maybe it's easier to do it afterwards with some standardized constrained Delaunay triangulation? //But that would also be stupid because then we could have used the constrained Delaunay from the beginning! return(triangulation); }
// // Are two triangles intersecting in 2d space // public static bool TriangleTriangle2D(Triangle2 t1, Triangle2 t2, bool do_AABB_test) { bool isIntersecting = false; //Step 0. AABB intersection which may speed up the algorithm if the triangles are far apart if (do_AABB_test) { //Rectangle that covers t1 AABB2 r1 = new AABB2(t1.MinX(), t1.MaxX(), t1.MinY(), t1.MaxY()); //Rectangle that covers t2 AABB2 r2 = new AABB2(t2.MinX(), t2.MaxX(), t2.MinY(), t2.MaxY()); if (!AABB_AABB_2D(r1, r2)) { return(false); } } //Step 1. Line-line instersection //Line 1 of t1 against all lines of t2 if ( LineLine(t1.p1, t1.p2, t2.p1, t2.p2, true) || LineLine(t1.p1, t1.p2, t2.p2, t2.p3, true) || LineLine(t1.p1, t1.p2, t2.p3, t2.p1, true) ) { isIntersecting = true; } //Line 2 of t1 against all lines of t2 else if ( LineLine(t1.p2, t1.p3, t2.p1, t2.p2, true) || LineLine(t1.p2, t1.p3, t2.p2, t2.p3, true) || LineLine(t1.p2, t1.p3, t2.p3, t2.p1, true) ) { isIntersecting = true; } //Line 3 of t1 against all lines of t2 else if ( LineLine(t1.p3, t1.p1, t2.p1, t2.p2, true) || LineLine(t1.p3, t1.p1, t2.p2, t2.p3, true) || LineLine(t1.p3, t1.p1, t2.p3, t2.p1, true) ) { isIntersecting = true; } //Now we can return if we are intersecting so we dont need to spend time testing something else if (isIntersecting) { return(isIntersecting); } //Step 2. Point-in-triangle intersection //We only need to test one corner from each triangle //If this point is not in the triangle, then the other points can't be in the triangle, because if this point is outside //and another point is inside, then the line between them would have been covered by step 1: line-line intersections test if (PointTriangle(t2, t1.p1, true) || PointTriangle(t1, t2.p1, true)) { isIntersecting = true; } return(isIntersecting); }
//We assume an edge is visible from a point if the triangle (formed by travering edges in the convex hull //of the existing triangulation) form a clockwise triangle with the point //https://stackoverflow.com/questions/8898255/check-whether-a-point-is-visible-from-a-face-of-a-2d-convex-hull private static HashSet <Triangle2> TriangulatePointsConvexHull(HashSet <MyVector2> points) { if (points.Count < 3) { Debug.Log("You need at least 3 points to form a triangle!"); return(null); } //Step 0. Init the triangles we will return HashSet <Triangle2> triangles = new HashSet <Triangle2>(); //Step 1. Sort the points List <MyVector2> sortedPoints = new List <MyVector2>(points); //OrderBy is always soring in ascending order - use OrderByDescending to get in the other order //sortedPoints = sortedPoints.OrderBy(n => n.x).ToList(); //If we have colinear points we have to sort in both x and y sortedPoints = sortedPoints.OrderBy(n => n.x).ThenBy(n => n.y).ToList(); //Step 2. Create the first triangle so we can start the algorithm because we need edges to look at //and see if they are visible //Pick the first two points in the sorted list - These are always a part of the first triangle MyVector2 p1 = sortedPoints[0]; MyVector2 p2 = sortedPoints[1]; //Remove them sortedPoints.RemoveAt(0); sortedPoints.RemoveAt(0); //The problem is the third point //If we have colinear points, then the third point in the sorted list is not always a valid point //to form a triangle because then it will be flat //So we have to look for a better point for (int i = 0; i < sortedPoints.Count; i++) { //We have found a non-colinear point LeftOnRight pointRelation = _Geometry.IsPoint_Left_On_Right_OfVector(p1, p2, sortedPoints[i]); if (pointRelation == LeftOnRight.Left || pointRelation == LeftOnRight.Right) { MyVector2 p3 = sortedPoints[i]; //Remove this point sortedPoints.RemoveAt(i); //Build the first triangle Triangle2 newTriangle = new Triangle2(p1, p2, p3); triangles.Add(newTriangle); break; } } //If we have finished search and not found a triangle, that means that all points //are colinear and we cant form any triangles if (triangles.Count == 0) { Debug.Log("All points you want to triangulate a co-linear"); return(null); } //Step 3. Add the other points one-by-one //For each point we add we have to calculate a convex hull of the previous points //An optimization is to not use all points in the triangulation //to calculate the hull because many of them might be inside of the hull //So we will use the previous points on the hull and add the point we added last iteration //to generate the new convex hull //TODO a faster way to test if an edge is visible is to use "plane test" accoding to Quickhull paper??? //On the other hand its faster to test just edges on the convex hull. The problem with using just planes //is that some points planes are "inside" of the convex polygon. Can we remove these points without //generating the convex hull? //First we need to init the convex hull HashSet <MyVector2> triangulatePoints = new HashSet <MyVector2>(); foreach (Triangle2 t in triangles) { triangulatePoints.Add(t.p1); triangulatePoints.Add(t.p2); triangulatePoints.Add(t.p3); } //Calculate the first convex hull List <MyVector2> pointsOnHull = _ConvexHull.JarvisMarch_2D(triangulatePoints); //Add the other points one-by-one foreach (MyVector2 pointToAdd in sortedPoints) { bool couldFormTriangle = false; //Loop through all edges in the convex hull for (int j = 0; j < pointsOnHull.Count; j++) { MyVector2 hull_p1 = pointsOnHull[j]; MyVector2 hull_p2 = pointsOnHull[MathUtility.ClampListIndex(j + 1, pointsOnHull.Count)]; //First we have to check if the points are colinear, then we cant form a triangle LeftOnRight pointRelation = _Geometry.IsPoint_Left_On_Right_OfVector(hull_p1, hull_p2, pointToAdd); if (pointRelation == LeftOnRight.On) { continue; } //If this triangle is clockwise, then we can see the edge //so we should create a new triangle with this edge and the point if (_Geometry.IsTriangleOrientedClockwise(hull_p1, hull_p2, pointToAdd)) { triangles.Add(new Triangle2(hull_p1, hull_p2, pointToAdd)); couldFormTriangle = true; } } //Add the point to the list of points on the hull //Will re-generate the hull by using these points so dont worry that the //list is not valid anymore if (couldFormTriangle) { pointsOnHull.Add(pointToAdd); //Find the convex hull of the current triangulation //It generates a counter-clockwise convex hull pointsOnHull = _ConvexHull.JarvisMarch_2D(new HashSet <MyVector2>(pointsOnHull)); } else { Debug.Log("This point could not form any triangles " + pointToAdd.x + " " + pointToAdd.y); } } return(triangles); }
//Used for debugging so we can see what's going on //public static List<MyVector2> GenerateConvexHull(List<MyVector2> originalPoints, bool includeColinearPoints, AABB normalizingbox, float dMax) public static List <MyVector2> GenerateConvexHull(List <MyVector2> originalPoints, bool includeColinearPoints) { List <MyVector2> pointsOnConvexHull = new List <MyVector2>(); //Step 1. //Find the extreme points along each axis //This is similar to AABB but we need both x and y coordinates at each extreme point MyVector2 maxX = originalPoints[0]; MyVector2 minX = originalPoints[0]; MyVector2 maxY = originalPoints[0]; MyVector2 minY = originalPoints[0]; for (int i = 1; i < originalPoints.Count; i++) { MyVector2 p = originalPoints[i]; if (p.x > maxX.x) { maxX = p; } if (p.x < minX.x) { minX = p; } if (p.y > maxY.y) { maxY = p; } if (p.y < minY.y) { minY = p; } } //Step 2. //From the 4 extreme points, choose the pair that's furthest appart //These two are the first two points on the hull List <MyVector2> extremePoints = new List <MyVector2>() { maxX, minX, maxY, minY }; //Just pick some points as start value MyVector2 p1 = maxX; MyVector2 p2 = minX; //Can use sqr because we are not interested in the exact distance float maxDistanceSqr = -Mathf.Infinity; //Loop through all points and compare them //TODO is comparing too many times: example maxX with maxY, and then maxY with maxX //https://stackoverflow.com/questions/12249051/unique-combinations-of-list for (int i = 0; i < extremePoints.Count; i++) { MyVector2 p1_test = extremePoints[i]; for (int j = 0; j < extremePoints.Count; j++) { //Dont compare the point with itself if (i == j) { continue; } MyVector2 p2_test = extremePoints[j]; float distSqr = MyVector2.SqrDistance(p1_test, p2_test); if (distSqr > maxDistanceSqr) { maxDistanceSqr = distSqr; p1 = p1_test; p2 = p2_test; } } } //Convert the list to hashset to easier remove points which are on the hull or are inside of the hull HashSet <MyVector2> pointsToAdd = new HashSet <MyVector2>(originalPoints); //Remove the first 2 points on the hull pointsToAdd.Remove(p1); pointsToAdd.Remove(p2); //Step 3. //Find the third point on the hull, by finding the point which is the furthest //from the line between p1 and p2 MyVector2 p3 = FindPointFurthestFromEdge(p1, p2, pointsToAdd); //Remove it from the points we want to add pointsToAdd.Remove(p3); //Step 4. Form the intitial triangle //Make sure the hull is oriented counter-clockwise Triangle2 tStart = new Triangle2(p1, p2, p3); if (_Geometry.IsTriangleOrientedClockwise(tStart.p1, tStart.p2, tStart.p3)) { tStart.ChangeOrientation(); } //New p1-p3 p1 = tStart.p1; p2 = tStart.p2; p3 = tStart.p3; //pointsOnConvexHull.Add(p1); //pointsOnConvexHull.Add(p2); //pointsOnConvexHull.Add(p3); //Remove the points that we now know are within the hull triangle RemovePointsWithinTriangle(tStart, pointsToAdd); //Step 5. //Associate the rest of the points to their closest edge HashSet <MyVector2> edge_p1p2_points = new HashSet <MyVector2>(); HashSet <MyVector2> edge_p2p3_points = new HashSet <MyVector2>(); HashSet <MyVector2> edge_p3p1_points = new HashSet <MyVector2>(); foreach (MyVector2 p in pointsToAdd) { //p1 p2 LeftOnRight pointRelation1 = _Geometry.IsPoint_Left_On_Right_OfVector(p1, p2, p); if (pointRelation1 == LeftOnRight.On || pointRelation1 == LeftOnRight.Right) { edge_p1p2_points.Add(p); continue; } //p2 p3 LeftOnRight pointRelation2 = _Geometry.IsPoint_Left_On_Right_OfVector(p2, p3, p); if (pointRelation2 == LeftOnRight.On || pointRelation2 == LeftOnRight.Right) { edge_p2p3_points.Add(p); continue; } //p3 p1 //If the point hasnt been added yet, we know it belong to this edge edge_p3p1_points.Add(p); } //Step 6 //For each edge, find the point furthest away and create a new triangle //and repeat the above steps by finding which points are inside of the hull //and which points are outside and belong to a new edge //Will automatically ignore the last point on this sub-hull to avoid doubles List <MyVector2> pointsOnHUll_p1p2 = CreateSubConvexHUll(p1, p2, edge_p1p2_points); List <MyVector2> pointsOnHUll_p2p3 = CreateSubConvexHUll(p2, p3, edge_p2p3_points); List <MyVector2> pointsOnHUll_p3p1 = CreateSubConvexHUll(p3, p1, edge_p3p1_points); //Create the final hull by combing the points pointsOnConvexHull.Clear(); pointsOnConvexHull.AddRange(pointsOnHUll_p1p2); pointsOnConvexHull.AddRange(pointsOnHUll_p2p3); pointsOnConvexHull.AddRange(pointsOnHUll_p3p1); //Step 7. Add colinear points //I think the easiest way to add colinear points is to add them when the algorithm is finished if (includeColinearPoints) { pointsOnConvexHull = AddColinearPoints(pointsOnConvexHull, originalPoints); } //Debug.Log("Points on hull: " + pointsOnConvexHull.Count); return(pointsOnConvexHull); }