// // Find the closest point on a line segment from a point // //From https://www.youtube.com/watch?v=KHuI9bXZS74 //Maybe better version https://stackoverflow.com/questions/3120357/get-closest-point-to-a-line public static MyVector2 GetClosestPointOnLineSegment(MyVector2 a, MyVector2 b, MyVector2 p) { MyVector2 a_p = p - a; MyVector2 a_b = b - a; //This is using vector projections??? //Square magnitude of AB vector float sqrMagnitudeAB = MyVector2.SqrMagnitude(a_b); //The DOT product of a_p and a_b float ABAPproduct = MyVector2.Dot(a_p, a_b); //The normalized "distance" from a to the closest point float distance = ABAPproduct / sqrMagnitudeAB; //This point may not be on the line segment, if so return one of the end points //Check if P projection is over vectorAB if (distance < 0) { return(a); } else if (distance > 1) { return(b); } else { return(a + a_b * distance); } }
//Insert intersection vertex private static ClipVertex InsertIntersectionVertex(MyVector2 a, MyVector2 b, MyVector2 intersectionPoint, ClipVertex currentVertex) { //Calculate alpha which is how far the intersection coordinate is between a and b //so we can insert this vertex at the correct position //pos = start + dir * alpha float alpha = MyVector2.SqrMagnitude(a - intersectionPoint) / MyVector2.SqrMagnitude(a - b); //Debug.Log(alpha); //Create a new vertex ClipVertex intersectionVertex = new ClipVertex(intersectionPoint); intersectionVertex.isIntersection = true; intersectionVertex.alpha = alpha; //Now we need to insert this intersection point somewhere after currentVertex ClipVertex insertAfterThisVertex = currentVertex; int safety = 0; while (true) { //If the next vertex is an intersectionvertex with a higher alpha //or if the next vertex is not an intersectionvertex, we cant improve, so break if (insertAfterThisVertex.next.alpha > alpha || !insertAfterThisVertex.next.isIntersection) { break; } insertAfterThisVertex = insertAfterThisVertex.next; safety += 1; if (safety > 100000) { Debug.Log("Stuck in loop in insert intersection vertices"); break; } } //Connect the vertex to the surrounding vertices intersectionVertex.next = insertAfterThisVertex.next; intersectionVertex.prev = insertAfterThisVertex; insertAfterThisVertex.next.prev = intersectionVertex; insertAfterThisVertex.next = intersectionVertex; return(intersectionVertex); }
// // Line-point calculations // //From https://stackoverflow.com/questions/3120357/get-closest-point-to-a-line //and https://www.youtube.com/watch?v=_ENEsV_kNx8 public static MyVector2 GetClosestPointOnLine(Edge2 e, MyVector2 p, bool withinSegment) { MyVector2 a = e.p1; MyVector2 b = e.p2; //Assume the line goes from a to b MyVector2 ab = b - a; //Vector from "start" of the line to the point outside of line MyVector2 ap = p - a; //Scalar projection https://en.wikipedia.org/wiki/Scalar_projection //The scalar projection is a scalar, equal to the length of the orthogonal projection of ap on ab, with a negative sign if the projection has an opposite direction with respect to ab. //scalarProjection = Dot(ap, ab) / Magnitude(ab) where the magnitude of ab is the distance between a and b //If ab is normalized, we get scalarProjection = Dot(ap, ab) //The distance from a to q (the closes point on the line): //float aq_distance = MyVector2.Dot(ap, ab) / MyVector2.Magnitude(ab); //To get the closest point on the line: //MyVector2 q = a + MyVector2.Normalize(ab) * aq_distance; //Can we do better? //Magnitude is defined as: Mathf.Sqrt((ab * ab)) //Normalization is defined as (ab / magnitude(ab)) //We get: q = a + (ab / magnitude(ab)) * (1 / magnitude(ab)) * dot(ap, ab) //Ignore the q and the dot and we get: (ab / Mathf.Sqrt((ab * ab))) * (1 / Mathf.Sqrt((ab * ab))) = ab / (ab * ab) //So we can use the square magnitude of ab and then we don't need to normalize ab (to get q), so we save two square roots, which is good because square root is a slow operation //The normalized "distance" from a to the closest point, so between 0 and 1 if we are within the line segment float distance = MyVector2.Dot(ap, ab) / MyVector2.SqrMagnitude(ab); //This point may not be on the line segment, if so return one of the end points float epsilon = MathUtility.EPSILON; if (withinSegment && distance < 0f - epsilon) { return(a); } else if (withinSegment && distance > 1f + epsilon) { return(b); } else { //This works because a_b is not normalized and distance is [0,1] if distance is within ab return(a + ab * distance); } }
// // Is a point p between point a and b (we assume all 3 are on the same line) // public static bool IsPointBetweenPoints(MyVector2 a, MyVector2 b, MyVector2 p) { bool isBetween = false; //Entire line segment MyVector2 ab = b - a; //The intersection and the first point MyVector2 ap = p - a; //Need to check 2 things: //1. If the vectors are pointing in the same direction = if the dot product is positive //2. If the length of the vector between the intersection and the first point is smaller than the entire line if (MyVector2.Dot(ab, ap) > 0f && MyVector2.SqrMagnitude(ab) >= MyVector2.SqrMagnitude(ap)) { isBetween = true; } return(isBetween); }
// // Calculate the center of circle in 2d space given three coordinates - Simple version // //From the book "Geometric Tools for Computer Graphics" public static MyVector2 CalculateCircleCenter(MyVector2 a, MyVector2 b, MyVector2 c) { //Make sure the triangle a-b-c is counterclockwise if (!IsTriangleOrientedClockwise(a, b, c)) { //Swap two vertices to change orientation (a, b) = (b, a); //Debug.Log("Swapped vertices"); } //The area of the triangle float X_1 = b.x - a.x; float X_2 = c.x - a.x; float Y_1 = b.y - a.y; float Y_2 = c.y - a.y; float A = 0.5f * MathUtility.Det2(X_1, Y_1, X_2, Y_2); //Debug.Log(A); //The center coordinates: //float L_10 = MyVector2.Magnitude(b - a); //float L_20 = MyVector2.Magnitude(c - a); //float L_10_square = L_10 * L_10; //float L_20_square = L_20 * L_20; float L_10_square = MyVector2.SqrMagnitude(b - a); float L_20_square = MyVector2.SqrMagnitude(c - a); float one_divided_by_4_A = 1f / (4f * A); float x = a.x + one_divided_by_4_A * ((Y_2 * L_10_square) - (Y_1 * L_20_square)); float y = a.y + one_divided_by_4_A * ((X_1 * L_20_square) - (X_2 * L_10_square)); MyVector2 center = new MyVector2(x, y); return(center); }
//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); }
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); }