//Is a point to the left or to the right of a vector going from a to b private void PointInRelationToVector(MyVector2 a, MyVector2 b, MyVector2 p) { //bool isToLeft = Geometry.IsPointLeftOfVector(a, b, p); //Debug.Log("Is to left: " + isToLeft); LeftOnRight value = _Geometry.IsPoint_Left_On_Right_OfVector(a, b, p); if (value == LeftOnRight.Left) { Debug.Log("Left"); } if (value == LeftOnRight.On) { Debug.Log("On"); } if (value == LeftOnRight.Right) { Debug.Log("Right"); } //Display Vector3 a_3d = a.ToVector3(); Vector3 b_3d = b.ToVector3(); Gizmos.DrawLine(a_3d, b_3d); float arrowSize = 0.1f; TestAlgorithmsHelpMethods.DisplayArrow(a_3d, b_3d, arrowSize, Color.white); Gizmos.DrawWireSphere(p.ToVector3(), 0.1f); }
//Help method to make code smaller //Is p to the right or on the line a-b private static bool IsPointToTheRightOrOnLine(MyVector2 a, MyVector2 b, MyVector2 p) { bool isToTheRight = false; LeftOnRight pointPos = _Geometry.IsPoint_Left_On_Right_OfVector(a, b, p); if (pointPos == LeftOnRight.Right || pointPos == LeftOnRight.On) { isToTheRight = true; } return(isToTheRight); }
//Add colinear points to the convex hull private static List <MyVector2> AddColinearPoints(List <MyVector2> pointsOnConvexHull, List <MyVector2> points) { List <MyVector2> pointsOnConvexHull_IncludingColinear = new List <MyVector2>(); //From the original points we dont have to remove the points that are on the convex hull //because they will be added anyway //Loop through all edges for (int i = 0; i < pointsOnConvexHull.Count; i++) { MyVector2 p1 = pointsOnConvexHull[i]; MyVector2 p2 = pointsOnConvexHull[MathUtility.ClampListIndex(i + 1, pointsOnConvexHull.Count)]; List <MyVector2> colinearPoints = new List <MyVector2>(); foreach (MyVector2 p in points) { LeftOnRight pointRelation = _Geometry.IsPoint_Left_On_Right_OfVector(p1, p2, p); if (pointRelation == LeftOnRight.On) { colinearPoints.Add(p); } } //Sort the colinear points so the are added on the correct order from p1 to p2 colinearPoints = colinearPoints.OrderBy(n => MyVector2.SqrMagnitude(n - p1)).ToList(); //Remove the last colinear point to avoid doubles colinearPoints.RemoveAt(colinearPoints.Count - 1); pointsOnConvexHull_IncludingColinear.AddRange(colinearPoints); } return(pointsOnConvexHull_IncludingColinear); }
//Split an edge and create a new sub-convex hull private static List <MyVector2> CreateSubConvexHUll(MyVector2 p1, MyVector2 p3, HashSet <MyVector2> pointsToAdd) { if (pointsToAdd.Count == 0) { //Never return the last point so we avoid doubles on the convex hull return(new List <MyVector2>() { p1 }); } //Find the point which is furthest from an edge MyVector2 p2 = FindPointFurthestFromEdge(p1, p3, pointsToAdd); //This point is also on the hull pointsToAdd.Remove(p2); //Remove points within this sub-hull triangle RemovePointsWithinTriangle(new Triangle2(p1, p2, p3), pointsToAdd); //No more points to add if (pointsToAdd.Count == 0) { //Never return the last point so we avoid doubles on the convex hull return(new List <MyVector2>() { p1, p2 }); } //If we still have points to add, we have to split the edges again else { //As before, find the points outside of each edge HashSet <MyVector2> edge_p1p2_points = new HashSet <MyVector2>(); HashSet <MyVector2> edge_p2p3_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 //If the point hasnt been added yet, we know it belong to this edge edge_p2p3_points.Add(p); } //Split the edge again List <MyVector2> pointsOnHUll_p1p2 = CreateSubConvexHUll(p1, p2, edge_p1p2_points); List <MyVector2> pointsOnHUll_p2p3 = CreateSubConvexHUll(p2, p3, edge_p2p3_points); //Combine the list List <MyVector2> pointsOnHull = pointsOnHUll_p1p2; pointsOnHull.AddRange(pointsOnHUll_p2p3); return(pointsOnHull); } }
//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); }
//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); }
public static List <MyVector2> GenerateConvexHull(List <MyVector2> points) { List <MyVector2> pointsOnConvexHull = new List <MyVector2>(); //Step 0. Normalize the data to range [0, 1] or everything will break at larger sizes :( //Make sure the data is already normalized!!! //Step 1. Find the vertex with the smallest x coordinate //If several points have the same x coordinate, find the one with the smallest y MyVector2 startPos = points[0]; for (int i = 1; i < points.Count; i++) { MyVector2 testPos = points[i]; //Because of precision issues, we use a small value to test if they are the same if (testPos.x < startPos.x || ((Mathf.Abs(testPos.x - startPos.x) < MathUtility.EPSILON && testPos.y < startPos.y))) { startPos = points[i]; } } //This vertex is always on the convex hull pointsOnConvexHull.Add(startPos); //But we can't remove it from the list of all points because we need it to stop the algorithm //points.Remove(startPos); //Step 2. Loop to find the other points on the hull MyVector2 previousPoint = pointsOnConvexHull[0]; int counter = 0; while (true) { //We might have colinear points, so we need a list to save all points added this iteration List <MyVector2> pointsToAddToTheHull = new List <MyVector2>(); //Pick next point randomly MyVector2 nextPoint = points[Random.Range(0, points.Count)]; //If we are coming from the first point on the convex hull //then we are not allowed to pick it as next point, so we have to try again if (previousPoint.Equals(pointsOnConvexHull[0]) && nextPoint.Equals(pointsOnConvexHull[0])) { counter += 1; continue; } //This point is assumed to be on the convex hull pointsToAddToTheHull.Add(nextPoint); //But this randomly selected point might not be the best next point, so we have to see if we can improve //by finding a point that is more to the right //We also have to check if this point has colinear points if it happens to be on the hull for (int i = 0; i < points.Count; i++) { MyVector2 testPoint = points[i]; //Dont test the point we picked randomly //Or the point we are coming from which might happen when we move from the first point on the hull if (testPoint.Equals(nextPoint) || testPoint.Equals(previousPoint)) { continue; } //Where is the test point in relation to the line between the point we are coming from //which we know is on the hull, and the point we think is on the hull LeftOnRight pointRelation = _Geometry.IsPoint_Left_On_Right_OfVector(previousPoint, nextPoint, testPoint); //The test point is on the line, so we have found a colinear point if (pointRelation == LeftOnRight.On) { pointsToAddToTheHull.Add(testPoint); } //To the right = better point, so pick it as next point we want to test if it is on the hull else if (pointRelation == LeftOnRight.Right) { nextPoint = testPoint; //Clear colinear points because they belonged to the old point which was worse pointsToAddToTheHull.Clear(); pointsToAddToTheHull.Add(nextPoint); //We dont have to start over because the previous points weve gone through were worse } //To the left = worse point so do nothing } //Sort this list, so we can add the colinear points in correct order pointsToAddToTheHull = pointsToAddToTheHull.OrderBy(n => MyVector2.SqrMagnitude(n - previousPoint)).ToList(); pointsOnConvexHull.AddRange(pointsToAddToTheHull); //Remove the points that are now on the convex hull, which should speed up the algorithm //Or will it be slower because it also takes some time to remove points? for (int i = 0; i < pointsToAddToTheHull.Count; i++) { points.Remove(pointsToAddToTheHull[i]); } //The point we are coming from in the next iteration previousPoint = pointsOnConvexHull[pointsOnConvexHull.Count - 1]; //Have we found the first point on the hull? If so we have completed the hull if (previousPoint.Equals(pointsOnConvexHull[0])) { //Then remove it because it is the same as the first point, and we want a convex hull with no duplicates pointsOnConvexHull.RemoveAt(pointsOnConvexHull.Count - 1); //Stop the loop! break; } //Safety if (counter > 100000) { Debug.Log("Stuck in endless loop when generating convex hull with jarvis march"); break; } counter += 1; } //Dont forget to unnormalize the points! return(pointsOnConvexHull); }
//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); }
// // Algorithm 1 // //If you have points on a convex hull, sorted one after each other //If you have colinear points, it will ignore some of them but still triangulate the entire area //Colinear points are not changing the shape public static HashSet <Triangle2> GetTriangles(List <MyVector2> pointsOnHull, bool addColinearPoints) { //If we hadnt have to deal with colinear points, this algorithm would be really simple: HashSet <Triangle2> triangles = new HashSet <Triangle2>(); //This vertex will be a vertex in all triangles MyVector2 a = pointsOnHull[0]; //And then we just loop through the other edges to make all triangles for (int i = 1; i < pointsOnHull.Count; i++) { MyVector2 b = pointsOnHull[i]; MyVector2 c = pointsOnHull[MathUtility.ClampListIndex(i + 1, pointsOnHull.Count)]; //Is this a valid triangle? //If a, b, c are on the same line, the triangle has no area and we can't add it LeftOnRight pointRelation = Geometry.IsPoint_Left_On_Right_OfVector(a, b, c); if (pointRelation == LeftOnRight.On) { continue; } triangles.Add(new Triangle2(a, b, c)); } //Add the missing colinear points by splitting triangles //Step 2.1. We have now triangulated the entire convex polygon, but if we have colinear points //some of those points were not added //We can add them by splitting triangles //Find colinear points if (addColinearPoints) { HashSet <MyVector2> colinearPoints = new HashSet <MyVector2>(pointsOnHull); //Remove points that are in the triangulation from the points on the convex hull //and we can see which where not added = the colinear points foreach (Triangle2 t in triangles) { colinearPoints.Remove(t.p1); colinearPoints.Remove(t.p2); colinearPoints.Remove(t.p3); } //Debug.Log("Colinear points: " + colinearPoints.Count); //Go through all colinear points and find which edge they should split //On the border we only need to split one edge because this edge has no neighbors foreach (MyVector2 p in colinearPoints) { foreach (Triangle2 t in triangles) { //Is this point in the triangle if (Intersections.PointTriangle(t, p, includeBorder: true)) { SplitTriangleEdge(t, p, triangles); break; } } } } return(triangles); }