// // Edge stuff // //Are two edges the same edge? private static bool AreTwoEdgesTheSame(MyVector2 e1_p1, MyVector2 e1_p2, MyVector2 e2_p1, MyVector2 e2_p2) { //Is e1_p1 part of this constraint? if ((e1_p1.Equals(e2_p1) || e1_p1.Equals(e2_p2))) { //Is e1_p2 part of this constraint? if ((e1_p2.Equals(e2_p1) || e1_p2.Equals(e2_p2))) { return(true); } } return(false); }
//Find the opposite edge to a vertex public Edge2 FindOppositeEdgeToVertex(MyVector2 p) { if (p.Equals(p1)) { return(new Edge2(p2, p3)); } else if (p.Equals(p2)) { return(new Edge2(p3, p1)); } else { return(new Edge2(p1, p2)); } }
//Get a list with unique edges //Currently we have two half-edges for each edge, making it time consuming //So this method is not always needed, but can be useful //But be careful because it takes time to generate this list as well, so measure that the algorithm is faster by using this list public HashSet <HalfEdge2> GetUniqueEdges() { HashSet <HalfEdge2> uniqueEdges = new HashSet <HalfEdge2>(); foreach (HalfEdge2 e in edges) { MyVector2 p1 = e.v.position; MyVector2 p2 = e.prevEdge.v.position; bool isInList = false; //TODO: Put these in a lookup dictionary to improve performance foreach (HalfEdge2 eUnique in uniqueEdges) { MyVector2 p1_test = eUnique.v.position; MyVector2 p2_test = eUnique.prevEdge.v.position; if ((p1.Equals(p1_test) && p2.Equals(p2_test)) || (p2.Equals(p1_test) && p1.Equals(p2_test))) { isInList = true; break; } } if (!isInList) { uniqueEdges.Add(e); } } return(uniqueEdges); }
//Get a list with unique edges //Currently we have two half-edges for each edge, making it time consuming //So this method is not always needed, but can be useful public List <HalfEdge2> GetUniqueEdges() { List <HalfEdge2> uniqueEdges = new List <HalfEdge2>(); foreach (HalfEdge2 e in edges) { MyVector2 p1 = e.v.position; MyVector2 p2 = e.prevEdge.v.position; bool isInList = false; for (int j = 0; j < uniqueEdges.Count; j++) { HalfEdge2 testEdge = uniqueEdges[j]; MyVector2 p1_test = testEdge.v.position; MyVector2 p2_test = testEdge.prevEdge.v.position; if ((p1.Equals(p1_test) && p2.Equals(p2_test)) || (p2.Equals(p1_test) && p1.Equals(p2_test))) { isInList = true; break; } } if (!isInList) { uniqueEdges.Add(e); } } return(uniqueEdges); }
//Is an edge crossing another edge? private static bool IsEdgeCrossingEdge(MyVector2 e1_p1, MyVector2 e1_p2, MyVector2 e2_p1, MyVector2 e2_p2) { //We will here run into floating point precision issues so we have to be careful //To solve that you can first check the end points //and modify the line-line intersection algorithm to include a small epsilon //First check if the edges are sharing a point, if so they are not crossing if (e1_p1.Equals(e2_p1) || e1_p1.Equals(e2_p2) || e1_p2.Equals(e2_p1) || e1_p2.Equals(e2_p2)) { return(false); } //Then check if the lines are intersecting if (!_Intersections.LineLine(new Edge2(e1_p1, e1_p2), new Edge2(e2_p1, e2_p2), includeEndPoints: false)) { return(false); } return(true); }
// // Split triangle edge // //Split an edge at a point on the edge to form four new triangles, while removing two old //public static void SplitTriangleEdge(HalfEdge e, Vector3 splitPosition) //{ //} // // Split triangle face // //Split a face (which we know is a triangle) at a point to create three new triangles while removing the old triangle //Could maybe make it more general so we can split a face, which consists of n edges public static void SplitTriangleFaceAtPoint(HalfEdgeFace2 f, MyVector2 splitPosition, HalfEdgeData2 data) { //The edges that belongs to this face HalfEdge2 e_1 = f.edge; HalfEdge2 e_2 = e_1.nextEdge; HalfEdge2 e_3 = e_2.nextEdge; //A list with new edges so we can connect the new edges with an edge on the opposite side HashSet <HalfEdge2> newEdges = new HashSet <HalfEdge2>(); CreateNewFace(e_1, splitPosition, data, newEdges); CreateNewFace(e_2, splitPosition, data, newEdges); CreateNewFace(e_3, splitPosition, data, newEdges); //Debug.Log("New edges " + newEdges.Count); //Find the opposite connections foreach (HalfEdge2 e in newEdges) { //If we have already found a opposite if (e.oppositeEdge != null) { continue; } MyVector2 eGoingTo = e.v.position; MyVector2 eGoingFrom = e.prevEdge.v.position; foreach (HalfEdge2 eOpposite in newEdges) { if (e == eOpposite || eOpposite.oppositeEdge != null) { continue; } MyVector2 eGoingTo_Other = eOpposite.v.position; MyVector2 eGoingFrom_Other = eOpposite.prevEdge.v.position; if (eGoingTo.Equals(eGoingFrom_Other) && eGoingFrom.Equals(eGoingTo_Other)) { e.oppositeEdge = eOpposite; //Might as well connect it from the other way as well eOpposite.oppositeEdge = e; //Debug.Log("Found opposite"); } } } //Delete the old triangle DeleteTriangleFace(f, data, false); }
//Find a triangle which has an edge going from p1 to p2 //private static HalfEdgeFace2 FindTriangleWithEdge(MyVector2 p1, MyVector2 p2, HalfEdgeData2 triangleData) //{ // HashSet<HalfEdge2> edges = triangleData.edges; // foreach (HalfEdge2 e in edges) // { // //An edge is going TO a vertex // MyVector2 e_p1 = e.prevEdge.v.position; // MyVector2 e_p2 = e.v.position; // if (e_p1.Equals(p1) && e_p2.Equals(p2)) // { // return e.face; // } // } // return null; //} //Find all half-edges that are constraint private static HashSet <HalfEdge2> FindAllConstraintEdges(List <MyVector2> constraints, HalfEdgeData2 triangleData) { HashSet <HalfEdge2> constrainEdges = new HashSet <HalfEdge2>(); //Create a new set with all constrains, and as we discover new constraints, we delete constrains, which will make searching faster //A constraint can only exist once! HashSet <Edge2> constraintsEdges = new HashSet <Edge2>(); for (int i = 0; i < constraints.Count; i++) { MyVector2 c_p1 = constraints[i]; MyVector2 c_p2 = constraints[MathUtility.ClampListIndex(i + 1, constraints.Count)]; constraintsEdges.Add(new Edge2(c_p1, c_p2)); } //All edges we have to search HashSet <HalfEdge2> edges = triangleData.edges; foreach (HalfEdge2 e in edges) { //An edge is going TO a vertex MyVector2 e_p1 = e.prevEdge.v.position; MyVector2 e_p2 = e.v.position; //Is this edge a constraint? foreach (Edge2 c_edge in constraintsEdges) { if (e_p1.Equals(c_edge.p1) && e_p2.Equals(c_edge.p2)) { constrainEdges.Add(e); constraintsEdges.Remove(c_edge); //Move on to the next edge break; } } //We have found all constraint, so don't need to search anymore if (constraintsEdges.Count == 0) { break; } } return(constrainEdges); }
//Find a triangle which has an edge going from p1 to p2 private static HalfEdgeFace2 FindTriangleWithEdge(MyVector2 p1, MyVector2 p2, HalfEdgeData2 triangleData) { HashSet <HalfEdge2> edges = triangleData.edges; foreach (HalfEdge2 e in edges) { //An edge is going TO a vertex MyVector2 e_p1 = e.prevEdge.v.position; MyVector2 e_p2 = e.v.position; if (e_p1.Equals(p1) && e_p2.Equals(p2)) { return(e.face); } } return(null); }
//Find which position in the list a vertex has //If there are multiple, we want the last one public int GetLastListPos(MyVector2 pos) { List <MyVector2> vertices = polygon.vertices; int listPos = -1; for (int i = 0; i < vertices.Count; i++) { if (pos.Equals(vertices[i])) { listPos = i; //In some cases there are multiple of this vertices and we want the last one //So we want stop searching after finding the first one //break; } } return(listPos); }
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); }
//Find all triangles that share a specific constraint private static HashSet <HalfEdgeFace2> FindAllTrianglesBorderingTheConstraint(List <MyVector2> constraints, HalfEdgeData2 triangleData) { HashSet <HalfEdgeFace2> facesOnTheBorder = new HashSet <HalfEdgeFace2>(); //Create a new set with all constrains, and as we discover new constraints, we delete constrains, which will make searching faster HashSet <Edge2> constraintsEdges = new HashSet <Edge2>(); for (int i = 0; i < constraints.Count; i++) { MyVector2 c_p1 = constraints[i]; MyVector2 c_p2 = constraints[MathUtility.ClampListIndex(i + 1, constraints.Count)]; constraintsEdges.Add(new Edge2(c_p1, c_p2)); } //Faster to search faces becase a triangle might have multiple constraints bordering it and we just need to find one HashSet <HalfEdgeFace2> faces = triangleData.faces; List <HalfEdge2> edges = new List <HalfEdge2>(); foreach (HalfEdgeFace2 f in faces) { edges.Clear(); edges.Add(f.edge); edges.Add(f.edge.nextEdge); edges.Add(f.edge.nextEdge.nextEdge); foreach (HalfEdge2 e in edges) { //An edge is going TO a vertex MyVector2 e_p1 = e.prevEdge.v.position; MyVector2 e_p2 = e.v.position; //Is this edge a constraint? bool foundConstraint = false; foreach (Edge2 c_edge in constraintsEdges) { if (e_p1.Equals(c_edge.p1) && e_p2.Equals(c_edge.p2)) { facesOnTheBorder.Add(f); constraintsEdges.Remove(c_edge); foundConstraint = true; break; } } if (foundConstraint) { break; } } if (constraintsEdges.Count == 0) { break; } } return(facesOnTheBorder); }
// // Try to restore the delaunay triangulation by flipping newly created edges // //This process is similar to when we created the original delaunay triangulation //This step can maybe be skipped if you just want a triangulation and Ive noticed its often not flipping any triangles private static void RestoreDelaunayTriangulation(MyVector2 c_p1, MyVector2 c_p2, List <HalfEdge2> newEdges) { int safety = 0; int flippedEdges = 0; //Repeat 4.1 - 4.3 until no further swaps take place while (true) { safety += 1; if (safety > 100000) { Debug.Log("Stuck in endless loop when delaunay after fixing constrained edges"); break; } bool hasFlippedEdge = false; //Step 4.1. Loop over each edge in the list of newly created edges for (int j = 0; j < newEdges.Count; j++) { HalfEdge2 e = newEdges[j]; //Step 4.2. Let the newly created edge be defined by the vertices MyVector2 v_k = e.v.position; MyVector2 v_l = e.prevEdge.v.position; //If this edge is equal to the constrained edge, then skip to step 4.1 //because we are not allowed to flip the constrained edge if ((v_k.Equals(c_p1) && v_l.Equals(c_p2)) || (v_l.Equals(c_p1) && v_k.Equals(c_p2))) { continue; } //Step 4.3. If the two triangles that share edge v_k and v_l don't satisfy the delaunay criterion, //so that a vertex of one of the triangles is inside the circumcircle of the other triangle, flip the edge //The third vertex of the triangle belonging to this edge MyVector2 v_third_pos = e.nextEdge.v.position; //The vertice belonging to the triangle on the opposite side of the edge and this vertex is not a part of the edge MyVector2 v_opposite_pos = e.oppositeEdge.nextEdge.v.position; //Test if we should flip this edge if (DelaunayMethods.ShouldFlipEdge(v_l, v_k, v_third_pos, v_opposite_pos)) { //Flip the edge hasFlippedEdge = true; HalfEdgeHelpMethods.FlipTriangleEdge(e); flippedEdges += 1; } } //We have searched through all edges and havent found an edge to flip, so we cant improve anymore if (!hasFlippedEdge) { Debug.Log("Found a constrained delaunay triangulation in " + flippedEdges + " flips"); break; } } }