// // Find edges that intersect with a constraint // //Method 1. Brute force by testing all unique edges //Find all edges of the current triangulation that intersects with the constraint edge between p1 and p2 private static Queue <HalfEdge2> FindIntersectingEdges_BruteForce(List <HalfEdge2> uniqueEdges, MyVector2 c_p1, MyVector2 c_p2) { //Should be in a queue because we will later plop the first in the queue and add edges in the back of the queue Queue <HalfEdge2> intersectingEdges = new Queue <HalfEdge2>(); //Loop through all edges and see if they are intersecting with the constrained edge for (int i = 0; i < uniqueEdges.Count; i++) { //The edges the triangle consists of HalfEdge2 e = uniqueEdges[i]; //The position the edge is going to MyVector2 e_p1 = e.v.position; //The position the edge is coming from MyVector2 e_p2 = e.prevEdge.v.position; //Is this edge intersecting with the constraint? if (IsEdgeCrossingEdge(e_p1, e_p2, c_p1, c_p2)) { //If so add it to the queue of edges intersectingEdges.Enqueue(e); } } return(intersectingEdges); }
//Method 2. Triangulation walk //This assumes there are no holes in the mesh //And that we have a super-triangle around the triangulation private static void FindIntersectingEdges_TriangleWalk(HalfEdgeData2 triangleData, MyVector2 c_p1, MyVector2 c_p2, List <HalfEdge2> intersectingEdges) { //Step 1. Begin at a triangle connected to the constraint edges's vertex c_p1 HalfEdgeFace2 f = null; foreach (HalfEdgeFace2 testFace in triangleData.faces) { //The edges the triangle consists of HalfEdge2 e1 = testFace.edge; HalfEdge2 e2 = e1.nextEdge; HalfEdge2 e3 = e2.nextEdge; //Does one of these edges include the first vertex in the constraint edge if (e1.v.position.Equals(c_p1) || e2.v.position.Equals(c_p1) || e3.v.position.Equals(c_p1)) { f = testFace; break; } } //Step2. Walk around p1 until we find a triangle with an edge that intersects with the edge p1-p2 //Step3. March from one triangle to the next in the general direction of 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 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); }
//Try to add a voronoi edge. Not all edges have a neighboring triangle, and if it hasnt we cant add a voronoi edge private static void TryAddVoronoiEdgeFromTriangleEdge(HalfEdge2 e, MyVector2 voronoiVertex, List <VoronoiEdge2> allEdges) { //Ignore if this edge has no neighboring triangle //If no opposite exists, we could maybe add a fake opposite to get an edge far away if (e.oppositeEdge == null) { return; } //Calculate the circumcenter of the neighbor HalfEdge2 eNeighbor = e.oppositeEdge; MyVector2 v1 = eNeighbor.v.position; MyVector2 v2 = eNeighbor.nextEdge.v.position; MyVector2 v3 = eNeighbor.nextEdge.nextEdge.v.position; MyVector2 voronoiVertexNeighbor = _Geometry.CalculateCircleCenter(v1, v2, v3); //Create a new voronoi edge between the voronoi vertices //Each edge in the half-edge data structure points TO a vertex, so this edge will be associated //with the vertex the edge is going from VoronoiEdge2 edge = new VoronoiEdge2(voronoiVertex, voronoiVertexNeighbor, sitePos: e.prevEdge.v.position); allEdges.Add(edge); }
//Insert a new point in the triangulation we already have, so we need at least one triangle public static void InsertNewPointInTriangulation(MyVector2 p, HalfEdgeData2 triangulationData, ref int missedPoints, ref int flippedEdges) { //Step 5. Insert the new point in the triangulation //Find the existing triangle the point is in HalfEdgeFace2 f = PointTriangulationIntersection.TriangulationWalk(p, null, triangulationData); //We couldnt find a triangle maybe because the point is not in the triangulation? if (f == null) { missedPoints += 1; } //Delete this triangle and form 3 new triangles by connecting p to each of the vertices in the old triangle HalfEdgeHelpMethods.SplitTriangleFaceAtPoint(f, p, triangulationData); //Step 6. Initialize stack. Place all triangles which are adjacent to the edges opposite p on a LIFO stack //The report says we should place triangles, but it's easier to place edges with our data structure Stack <HalfEdge2> trianglesToInvestigate = new Stack <HalfEdge2>(); AddTrianglesOppositePToStack(p, trianglesToInvestigate, triangulationData); //Step 7. Restore delaunay triangulation //While the stack is not empty int safety = 0; while (trianglesToInvestigate.Count > 0) { safety += 1; if (safety > 1000000) { Debug.Log("Stuck in infinite loop when restoring delaunay in incremental sloan algorithm"); break; } //Step 7.1. Remove a triangle from the stack HalfEdge2 edgeToTest = trianglesToInvestigate.Pop(); //Step 7.2. Do we need to flip this edge? //If p is outside or on the circumcircle for this triangle, we have a delaunay triangle and can return to next loop MyVector2 a = edgeToTest.v.position; MyVector2 b = edgeToTest.prevEdge.v.position; MyVector2 c = edgeToTest.nextEdge.v.position; //abc are here counter-clockwise if (DelaunayMethods.ShouldFlipEdgeStable(a, b, c, p)) { HalfEdgeHelpMethods.FlipTriangleEdge(edgeToTest); //Step 7.3. Place any triangles which are now opposite p on the stack AddTrianglesOppositePToStack(p, trianglesToInvestigate, triangulationData); flippedEdges += 1; } } }
// // 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 all triangles opposite of vertex p //But we will find all edges opposite to p, and from these edges we can find the triangles private static void AddTrianglesOppositePToStack(MyVector2 p, Stack <HalfEdge2> trianglesOppositeP, HalfEdgeData2 triangulationData) { //Find a vertex at position p and then rotate around it, triangle-by-triangle, to find all opposite edges HalfEdgeVertex2 rotateAroundThis = null; foreach (HalfEdgeVertex2 v in triangulationData.vertices) { if (v.position.Equals(p)) { rotateAroundThis = v; } } //Which triangle is this vertex a part of, so we know when we have rotated all the way around HalfEdgeFace2 tStart = rotateAroundThis.edge.face; HalfEdgeFace2 tCurrent = null; int safety = 0; while (tCurrent != tStart) { safety += 1; if (safety > 10000) { Debug.Log("Stuck in endless loop when finding opposite edges in Delaunay Sloan"); break; } //The edge opposite to p HalfEdge2 edgeOppositeRotateVertex = rotateAroundThis.edge.nextEdge.oppositeEdge; //Try to add the edge to the list iof triangles we are interested in //Null might happen if we are at the border //A stack might include duplicates so we have to check for that as well if (edgeOppositeRotateVertex != null && !trianglesOppositeP.Contains(edgeOppositeRotateVertex)) { trianglesOppositeP.Push(edgeOppositeRotateVertex); } //Rotate left - this assumes we can always rotate left so no holes are allowed //and neither can we investigate one of the vertices thats a part of the supertriangle //which we dont need to worry about because p is never a part of the supertriangle rotateAroundThis = rotateAroundThis.edge.oppositeEdge.v; //In which triangle are we now? tCurrent = rotateAroundThis.edge.face; } }
// // Delete a triangle // public static void DeleteTriangleFace(HalfEdgeFace2 t, HalfEdgeData2 data, bool shouldSetOppositeToNull) { //Update the data structure //In the half-edge data structure there's an edge going in the opposite direction //on the other side of this triangle with a reference to this edge, so we have to set these to null HalfEdge2 t_e1 = t.edge; HalfEdge2 t_e2 = t_e1.nextEdge; HalfEdge2 t_e3 = t_e2.nextEdge; //If we want to remove the triangle and create a hole //But sometimes we have created a new triangle and then we cant set the opposite to null if (shouldSetOppositeToNull) { if (t_e1.oppositeEdge != null) { t_e1.oppositeEdge.oppositeEdge = null; } if (t_e2.oppositeEdge != null) { t_e2.oppositeEdge.oppositeEdge = null; } if (t_e3.oppositeEdge != null) { t_e3.oppositeEdge.oppositeEdge = null; } } //Remove from the data structure //Remove from the list of all triangles data.faces.Remove(t); //Remove the edges from the list of all edges data.edges.Remove(t_e1); data.edges.Remove(t_e2); data.edges.Remove(t_e3); //Remove the vertices data.vertices.Remove(t_e1.v); data.vertices.Remove(t_e2.v); data.vertices.Remove(t_e3.v); }
//Try to add a voronoi edge. Not all edges have a neighboring triangle, and if it hasnt we cant add a voronoi edge private static void TryAddVoronoiEdgeFromTriangleEdge(HalfEdge2 e, MyVector2 voronoiVertex, List <VoronoiEdge2> allEdges) { //Ignore if this edge has no neighboring triangle if (e.oppositeEdge == null) { return; } //Calculate the circumcenter of the neighbor HalfEdge2 eNeighbor = e.oppositeEdge; MyVector2 v1 = eNeighbor.v.position; MyVector2 v2 = eNeighbor.nextEdge.v.position; MyVector2 v3 = eNeighbor.nextEdge.nextEdge.v.position; MyVector2 voronoiVertexNeighbor = _Geometry.CalculateCircleCenter(v1, v2, v3); //Create a new vornoi edge between the voronoi vertices VoronoiEdge2 edge = new VoronoiEdge2(voronoiVertex, voronoiVertexNeighbor, sitePos: e.prevEdge.v.position); allEdges.Add(edge); }
// // 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; } } }
public static HashSet <VoronoiCell2> GenerateVoronoiDiagram(HashSet <MyVector2> sites) { //First generate the delaunay triangulation //This one has caused a bug so should be avoided //HalfEdgeData2 data = _Delaunay.FlippingEdges(sites, new HalfEdgeData2()); //This one is faster and more accurate, so use it. But if you are using it, make sure to normalize the sites! HalfEdgeData2 delaunayTriangulation = _Delaunay.PointByPoint(sites, new HalfEdgeData2()); //Generate the voronoi diagram //Step 1. For every delaunay edge, compute a voronoi edge //The voronoi edge is the edge connecting the circumcenters of two neighboring delaunay triangles List <VoronoiEdge2> voronoiEdges = new List <VoronoiEdge2>(); HashSet <HalfEdgeFace2> triangles = delaunayTriangulation.faces; //Loop through each triangle foreach (HalfEdgeFace2 t in triangles) { //Each triangle consists of these edges HalfEdge2 e1 = t.edge; HalfEdge2 e2 = e1.nextEdge; HalfEdge2 e3 = e2.nextEdge; //Calculate the circumcenter for this triangle MyVector2 v1 = e1.v.position; MyVector2 v2 = e2.v.position; MyVector2 v3 = e3.v.position; //The circumcenter is the center of a circle where the triangles corners is on the circumference of that circle //The circumcenter is also known as a voronoi vertex, which is a position in the diagram where we are equally //close to the surrounding sites (= the corners ina voronoi cell) MyVector2 voronoiVertex = _Geometry.CalculateCircleCenter(v1, v2, v3); //Debug.Log(voronoiVertex.x + " " + voronoiVertex.y); //We will generate a single edge belonging to this site //Try means that this edge might not have an opposite and then we can't generate an edge TryAddVoronoiEdgeFromTriangleEdge(e1, voronoiVertex, voronoiEdges); TryAddVoronoiEdgeFromTriangleEdge(e2, voronoiVertex, voronoiEdges); TryAddVoronoiEdgeFromTriangleEdge(e3, voronoiVertex, voronoiEdges); } //Step 2. Find the voronoi cells where each cell is a list of all edges belonging to a site //So we have a lot of edges and now each edge should get a cell //These edges are not sorted, so they are added as we find them HashSet <VoronoiCell2> voronoiCells = new HashSet <VoronoiCell2>(); for (int i = 0; i < voronoiEdges.Count; i++) { VoronoiEdge2 e = voronoiEdges[i]; //Find the cell in the list of all cells that includes this site VoronoiCell2 cell = TryFindCell(e, voronoiCells); //No cell was found so we need to create a new cell if (cell == null) { VoronoiCell2 newCell = new VoronoiCell2(e.sitePos); voronoiCells.Add(newCell); newCell.edges.Add(e); } else { cell.edges.Add(e); } } return(voronoiCells); }
public static List <VoronoiCell2> GenerateVoronoiDiagram(HashSet <MyVector2> sites) { //First generate the delaunay triangulation //This one has caused a bug so should be avoided //HalfEdgeData2 data = _Delaunay.FlippingEdges(sites, new HalfEdgeData2()); //This one is faster and more accurate, so use it. But if you are using it, make sure to normalize the sites! HalfEdgeData2 data = _Delaunay.PointByPoint(sites, new HalfEdgeData2()); //Generate the voronoi diagram //Step 1. For every delaunay edge, compute a voronoi edge //The voronoi edge is the edge connecting the circumcenters of two neighboring delaunay triangles List <VoronoiEdge2> voronoiEdges = new List <VoronoiEdge2>(); HashSet <HalfEdgeFace2> triangles = data.faces; foreach (HalfEdgeFace2 t in triangles) { //Each triangle consists of these edges HalfEdge2 e1 = t.edge; HalfEdge2 e2 = e1.nextEdge; HalfEdge2 e3 = e2.nextEdge; //Calculate the circumcenter for this triangle MyVector2 v1 = e1.v.position; MyVector2 v2 = e2.v.position; MyVector2 v3 = e3.v.position; //The circumcenter is the center of a circle where the triangles corners is on the circumference of that circle //The circumcenter is also known as a voronoi vertex, which is a position in the diagram where we are equally //close to the surrounding sites MyVector2 voronoiVertex = _Geometry.CalculateCircleCenter(v1, v2, v3); //This will generate double edges - one belonging to each site, and could maybe be improved in the future //by using the half-edge data structure TryAddVoronoiEdgeFromTriangleEdge(e1, voronoiVertex, voronoiEdges); TryAddVoronoiEdgeFromTriangleEdge(e2, voronoiVertex, voronoiEdges); TryAddVoronoiEdgeFromTriangleEdge(e3, voronoiVertex, voronoiEdges); } //Step 2. Find the voronoi cells where each cell is a list of all edges belonging to a site List <VoronoiCell2> voronoiCells = new List <VoronoiCell2>(); for (int i = 0; i < voronoiEdges.Count; i++) { VoronoiEdge2 e = voronoiEdges[i]; //Find the position in the list of all cells that includes this site int cellPos = TryFindCellPos(e, voronoiCells); //No cell was found so we need to create a new cell if (cellPos == -1) { VoronoiCell2 newCell = new VoronoiCell2(e.sitePos); voronoiCells.Add(newCell); newCell.edges.Add(e); } else { voronoiCells[cellPos].edges.Add(e); } } return(voronoiCells); }
// // Alternative 2. Triangulation walk // //Fast but a little more complicated to understand //We can also give it a list, which should be empty so we can display the triangulation walk public static HalfEdgeFace2 TriangulationWalk(MyVector2 p, HalfEdgeFace2 startTriangle, HalfEdgeData2 triangulationData, List <HalfEdgeFace2> visitedTriangles = null) { HalfEdgeFace2 intersectingTriangle = null; //If we have a triangle to start in which may speed up the algorithm HalfEdgeFace2 currentTriangle = null; //We can feed it a start triangle to sometimes make the algorithm faster if (startTriangle != null) { currentTriangle = startTriangle; } //Find a random start triangle which is faster than starting at the first triangle? else { int randomPos = Random.Range(0, triangulationData.faces.Count); int i = 0; //faces are stored in a hashset so we have to loop through them while counting //to find the start triangle foreach (HalfEdgeFace2 f in triangulationData.faces) { if (i == randomPos) { currentTriangle = f; break; } i += 1; } } if (currentTriangle == null) { Debug.Log("Couldnt find start triangle when walking in triangulation"); return(null); } if (visitedTriangles != null) { visitedTriangles.Add(currentTriangle); } //Start the triangulation walk to find the intersecting triangle int safety = 0; while (true) { safety += 1; if (safety > 1000000) { Debug.Log("Stuck in endless loop when walking in triangulation"); break; } //Is the point intersecting with the current triangle? //We need to do 3 tests where each test is using the triangles edges //If the point is to the right of all edges, then it's inside the triangle //If the point is to the left we jump to that triangle instead HalfEdge2 e1 = currentTriangle.edge; HalfEdge2 e2 = e1.nextEdge; HalfEdge2 e3 = e2.nextEdge; //Test 1 if (IsPointToTheRightOrOnLine(e1.prevEdge.v.position, e1.v.position, p)) { //Test 2 if (IsPointToTheRightOrOnLine(e2.prevEdge.v.position, e2.v.position, p)) { //Test 3 if (IsPointToTheRightOrOnLine(e3.prevEdge.v.position, e3.v.position, p)) { //We have found the triangle the point is in intersectingTriangle = currentTriangle; break; } //If to the left, move to this triangle else { currentTriangle = e3.oppositeEdge.face; } } //If to the left, move to this triangle else { currentTriangle = e2.oppositeEdge.face; } } //If to the left, move to this triangle else { currentTriangle = e1.oppositeEdge.face; } if (visitedTriangles != null) { visitedTriangles.Add(currentTriangle); } } //Add the last triangle if we found it if (visitedTriangles != null && intersectingTriangle != null) { visitedTriangles.Add(intersectingTriangle); } return(intersectingTriangle); }
//Create a new triangle face when splitting triangle face private static void CreateNewFace(HalfEdge2 e_old, MyVector2 splitPosition, HalfEdgeData2 data, HashSet <HalfEdge2> newEdges) { //This triangle has the following positons MyVector2 p_split = splitPosition; MyVector2 p_next = e_old.prevEdge.v.position; MyVector2 p_prev = e_old.v.position; //Create the new stuff HalfEdgeVertex2 v_split = new HalfEdgeVertex2(p_split); HalfEdgeVertex2 v_next = new HalfEdgeVertex2(p_next); HalfEdgeVertex2 v_prev = new HalfEdgeVertex2(p_prev); //This is the edge that has the same position as the old edge HalfEdge2 e_1 = new HalfEdge2(v_prev); HalfEdge2 e_2 = new HalfEdge2(v_split); HalfEdge2 e_3 = new HalfEdge2(v_next); //The new face HalfEdgeFace2 f = new HalfEdgeFace2(e_1); //Create the connections //The new edge e has the same opposite as the old edge e_1.oppositeEdge = e_old.oppositeEdge; //But the opposite edge needs a new reference to this edge if its not a border if (e_1.oppositeEdge != null) { e_old.oppositeEdge.oppositeEdge = e_1; } //The other new edges will find the opposite in a loop when we have created all new edges newEdges.Add(e_2); newEdges.Add(e_3); //Create the connections between the edges e_1.nextEdge = e_2; e_1.prevEdge = e_3; e_2.nextEdge = e_3; e_2.prevEdge = e_1; e_3.nextEdge = e_1; e_3.prevEdge = e_2; //Each edge needs to connect to a face e_1.face = f; e_2.face = f; e_3.face = f; //The vertices need an edge that starts at that point v_split.edge = e_3; v_next.edge = e_1; v_prev.edge = e_2; //Add them to the lists data.faces.Add(f); data.edges.Add(e_1); data.edges.Add(e_2); data.edges.Add(e_3); data.vertices.Add(v_split); data.vertices.Add(v_next); data.vertices.Add(v_prev); }
// // Triangle to half-edge // public static HalfEdgeData2 Triangle2ToHalfEdge2(HashSet <Triangle2> triangles, HalfEdgeData2 data) { //Make sure the triangles have the same orientation, which is clockwise triangles = HelpMethods.OrientTrianglesClockwise(triangles); //Fill the data structure foreach (Triangle2 t in triangles) { HalfEdgeVertex2 v1 = new HalfEdgeVertex2(t.p1); HalfEdgeVertex2 v2 = new HalfEdgeVertex2(t.p2); HalfEdgeVertex2 v3 = new HalfEdgeVertex2(t.p3); //The vertices the edge points to HalfEdge2 he1 = new HalfEdge2(v1); HalfEdge2 he2 = new HalfEdge2(v2); HalfEdge2 he3 = new HalfEdge2(v3); he1.nextEdge = he2; he2.nextEdge = he3; he3.nextEdge = he1; he1.prevEdge = he3; he2.prevEdge = he1; he3.prevEdge = he2; //The vertex needs to know of an edge going from it v1.edge = he2; v2.edge = he3; v3.edge = he1; //The face the half-edge is connected to HalfEdgeFace2 face = new HalfEdgeFace2(he1); //Each edge needs to know of the face connected to this edge he1.face = face; he2.face = face; he3.face = face; //Add everything to the lists data.edges.Add(he1); data.edges.Add(he2); data.edges.Add(he3); data.faces.Add(face); data.vertices.Add(v1); data.vertices.Add(v2); data.vertices.Add(v3); } //Step 4. Find the half-edges going in the opposite direction of each edge we have //Is there a faster way to do this because this is the bottleneck? foreach (HalfEdge2 e in data.edges) { HalfEdgeVertex2 goingToVertex = e.v; HalfEdgeVertex2 goingFromVertex = e.prevEdge.v; foreach (HalfEdge2 eOther in data.edges) { //Dont compare with itself if (e == eOther) { continue; } //Is this edge going between the vertices in the opposite direction if (goingFromVertex.position.Equals(eOther.v.position) && goingToVertex.position.Equals(eOther.prevEdge.v.position)) { e.oppositeEdge = eOther; break; } } } return(data); }
public HalfEdgeFace2(HalfEdge2 edge) { this.edge = edge; }
// // Find which triangles are within a constraint // public static HashSet <HalfEdgeFace2> FindTrianglesWithinConstraint(HalfEdgeData2 triangleData, List <MyVector2> constraints) { HashSet <HalfEdgeFace2> trianglesToDelete = new HashSet <HalfEdgeFace2>(); //Step 1. Find a triangle with an edge that shares an edge with the first constraint edge in the list //Since both are clockwise we know we are "inside" of the constraint, so this is a triangle we should delete HalfEdgeFace2 borderTriangle = null; MyVector2 c_p1 = constraints[0]; MyVector2 c_p2 = constraints[1]; //Search through all triangles foreach (HalfEdgeFace2 t in triangleData.faces) { //The edges in this triangle HalfEdge2 e1 = t.edge; HalfEdge2 e2 = e1.nextEdge; HalfEdge2 e3 = e2.nextEdge; //Is any of these edges a constraint? If so we have find the first triangle if (e1.v.position.Equals(c_p2) && e1.prevEdge.v.position.Equals(c_p1)) { borderTriangle = t; break; } if (e2.v.position.Equals(c_p2) && e2.prevEdge.v.position.Equals(c_p1)) { borderTriangle = t; break; } if (e3.v.position.Equals(c_p2) && e3.prevEdge.v.position.Equals(c_p1)) { borderTriangle = t; break; } } if (borderTriangle == null) { return(null); } //Step 2. Find the rest of the triangles within the constraint by using a flood fill algorithm //Maybe better to first find all the other border triangles? //We know this triangle should be deleted trianglesToDelete.Add(borderTriangle); //Store the triangles we flood filling in this queue Queue <HalfEdgeFace2> trianglesToCheck = new Queue <HalfEdgeFace2>(); //Start at the triangle we know is within the constraints trianglesToCheck.Enqueue(borderTriangle); int safety = 0; while (true) { safety += 1; if (safety > 100000) { Debug.Log("Stuck in infinite loop when looking for triangles within constraint"); break; } //Stop if we are out of neighbors if (trianglesToCheck.Count == 0) { break; } //Pick the first triangle in the list and investigate its neighbors HalfEdgeFace2 t = trianglesToCheck.Dequeue(); //Investigate the triangles on the opposite sides of these edges HalfEdge2 e1 = t.edge; HalfEdge2 e2 = e1.nextEdge; HalfEdge2 e3 = e2.nextEdge; //A triangle is a neighbor within the constraint if: //- The neighbor is not an outer border meaning no neighbor exists //- If we have not already visited the neighbor //- If the edge between the neighbor and this triangle is not a constraint if (e1.oppositeEdge != null && !trianglesToDelete.Contains(e1.oppositeEdge.face) && !trianglesToCheck.Contains(e1.oppositeEdge.face) && !IsEdgeAConstraint(e1.v.position, e1.prevEdge.v.position, constraints)) { trianglesToCheck.Enqueue(e1.oppositeEdge.face); trianglesToDelete.Add(e1.oppositeEdge.face); } if (e2.oppositeEdge != null && !trianglesToDelete.Contains(e2.oppositeEdge.face) && !trianglesToCheck.Contains(e2.oppositeEdge.face) && !IsEdgeAConstraint(e2.v.position, e2.prevEdge.v.position, constraints)) { trianglesToCheck.Enqueue(e2.oppositeEdge.face); trianglesToDelete.Add(e2.oppositeEdge.face); } if (e3.oppositeEdge != null && !trianglesToDelete.Contains(e3.oppositeEdge.face) && !trianglesToCheck.Contains(e3.oppositeEdge.face) && !IsEdgeAConstraint(e3.v.position, e3.prevEdge.v.position, constraints)) { trianglesToCheck.Enqueue(e3.oppositeEdge.face); trianglesToDelete.Add(e3.oppositeEdge.face); } } return(trianglesToDelete); }
// // Remove the edges that intersects with a constraint by flipping triangles // //The idea here is that all possible triangulations for a set of points can be found //by systematically swapping the diagonal in each convex quadrilateral formed by a pair of triangles //So we will test all possible arrangements and will always find a triangulation which includes the constrained edge private static List <HalfEdge2> RemoveIntersectingEdges(MyVector2 v_i, MyVector2 v_j, Queue <HalfEdge2> intersectingEdges) { List <HalfEdge2> newEdges = new List <HalfEdge2>(); int safety = 0; //While some edges still cross the constrained edge, do steps 3.1 and 3.2 while (intersectingEdges.Count > 0) { safety += 1; if (safety > 100000) { Debug.Log("Stuck in infinite loop when fixing constrained edges"); break; } //Step 3.1. Remove an edge from the list of edges that intersects the constrained edge HalfEdge2 e = intersectingEdges.Dequeue(); //The vertices belonging to the two triangles MyVector2 v_k = e.v.position; MyVector2 v_l = e.prevEdge.v.position; MyVector2 v_3rd = e.nextEdge.v.position; //The vertex belonging to the opposite triangle and isn't shared by the current edge MyVector2 v_opposite_pos = e.oppositeEdge.nextEdge.v.position; //Step 3.2. If the two triangles don't form a convex quadtrilateral //place the edge back on the list of intersecting edges (because this edge cant be flipped) //and go to step 3.1 if (!_Geometry.IsQuadrilateralConvex(v_k, v_l, v_3rd, v_opposite_pos)) { intersectingEdges.Enqueue(e); continue; } else { //Flip the edge like we did when we created the delaunay triangulation HalfEdgeHelpMethods.FlipTriangleEdge(e); //The new diagonal is defined by the vertices MyVector2 v_m = e.v.position; MyVector2 v_n = e.prevEdge.v.position; //If this new diagonal intersects with the constrained edge, add it to the list of intersecting edges if (IsEdgeCrossingEdge(v_i, v_j, v_m, v_n)) { intersectingEdges.Enqueue(e); } //Place it in the list of newly created edges else { newEdges.Add(e); } } } return(newEdges); }
// // Flip triangle edge // //So the edge shared by two triangles is going between the two other vertices originally not part of the edge public static void FlipTriangleEdge(HalfEdge2 e) { //The data we need //This edge's triangle edges HalfEdge2 e_1 = e; HalfEdge2 e_2 = e_1.nextEdge; HalfEdge2 e_3 = e_1.prevEdge; //The opposite edge's triangle edges HalfEdge2 e_4 = e_1.oppositeEdge; HalfEdge2 e_5 = e_4.nextEdge; HalfEdge2 e_6 = e_4.prevEdge; //The 4 vertex positions MyVector2 aPos = e_1.v.position; MyVector2 bPos = e_2.v.position; MyVector2 cPos = e_3.v.position; MyVector2 dPos = e_5.v.position; //The 6 old vertices, we can use HalfEdgeVertex2 a_old = e_1.v; HalfEdgeVertex2 b_old = e_1.nextEdge.v; HalfEdgeVertex2 c_old = e_1.prevEdge.v; HalfEdgeVertex2 a_opposite_old = e_4.prevEdge.v; HalfEdgeVertex2 c_opposite_old = e_4.v; HalfEdgeVertex2 d_old = e_4.nextEdge.v; //Flip //Vertices //Triangle 1: b-c-d HalfEdgeVertex2 b = b_old; HalfEdgeVertex2 c = c_old; HalfEdgeVertex2 d = d_old; //Triangle 1: b-d-a HalfEdgeVertex2 b_opposite = a_opposite_old; b_opposite.position = bPos; HalfEdgeVertex2 d_opposite = c_opposite_old; d_opposite.position = dPos; HalfEdgeVertex2 a = a_old; //Change half-edge - half-edge connections e_1.nextEdge = e_3; e_1.prevEdge = e_5; e_2.nextEdge = e_4; e_2.prevEdge = e_6; e_3.nextEdge = e_5; e_3.prevEdge = e_1; e_4.nextEdge = e_6; e_4.prevEdge = e_2; e_5.nextEdge = e_1; e_5.prevEdge = e_3; e_6.nextEdge = e_2; e_6.prevEdge = e_4; //Half-edge - vertex connection e_1.v = b; e_2.v = b_opposite; e_3.v = c; e_4.v = d_opposite; e_5.v = d; e_6.v = a; //Half-edge - face connection HalfEdgeFace2 f1 = e_1.face; HalfEdgeFace2 f2 = e_4.face; e_1.face = f1; e_3.face = f1; e_5.face = f1; e_2.face = f2; e_4.face = f2; e_6.face = f2; //Face - half-edge connection f1.edge = e_3; f2.edge = e_4; //Vertices connection, which should have a reference to a half-edge going away from the vertex //Triangle 1: b-c-d b.edge = e_3; c.edge = e_5; d.edge = e_1; //Triangle 1: b-d-a b_opposite.edge = e_4; d_opposite.edge = e_6; a.edge = e_2; //Opposite-edges are not changing! //And neither are we adding, removing data so we dont need to update the lists with all data }