//Flip edges until we get a delaunay triangulation private static void FlipEdges(HalfEdgeData2 triangleData) { //The edges we want to flip HashSet <HalfEdge2> edges = triangleData.edges; //To avoid getting stuck in infinite loop int safety = 0; //Count how many edges we have flipped, which may be interesting to display int flippedEdges = 0; while (true) { safety += 1; if (safety > 100000) { Debug.Log("Stuck in endless loop when flipping edges to get a Delaunay triangulation"); break; } bool hasFlippedEdge = false; //Search through all edges to see if we can flip an edge foreach (HalfEdge2 thisEdge in edges) { //Is this edge sharing an edge with another triangle, otherwise its a border, and then we cant flip the edge if (thisEdge.oppositeEdge == null) { continue; } //The positions of the vertices belonging to the two triangles that we might flip //a-c should be the edge that we might flip MyVector2 a = thisEdge.v.position; MyVector2 b = thisEdge.nextEdge.v.position; MyVector2 c = thisEdge.nextEdge.nextEdge.v.position; MyVector2 d = thisEdge.oppositeEdge.nextEdge.v.position; //Test if we should flip this edge if (DelaunayMethods.ShouldFlipEdge(a, b, c, d)) { flippedEdges += 1; hasFlippedEdge = true; HalfEdgeHelpMethods.FlipTriangleEdge(thisEdge); } } //We have searched through all edges and havent found an edge to flip, so we have a Delaunay triangulation! if (!hasFlippedEdge) { Debug.Log("Found a delaunay triangulation in " + flippedEdges + " flips"); break; } } }
//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); } }
//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 }
//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; } } }
//HalfEdgeData2 public static HalfEdgeData2 UnNormalize(HalfEdgeData2 data, AABB2 aabb, float dMax) { foreach (HalfEdgeVertex2 v in data.vertices) { MyVector2 vUnNormalized = HelpMethods.UnNormalize(v.position, aabb, dMax); v.position = vUnNormalized; } return(data); }
//HalfEdgeData2 public HalfEdgeData2 UnNormalize(HalfEdgeData2 data) { foreach (HalfEdgeVertex2 v in data.vertices) { MyVector2 vUnNormalized = UnNormalize(v.position); v.position = vUnNormalized; } return(data); }
// // Add the constraints to the delaunay triangulation // private static HalfEdgeData2 AddConstraints(HalfEdgeData2 triangleData, List <MyVector2> constraints, bool shouldRemoveTriangles) { //Validate the data if (constraints == null) { return(triangleData); } //First create a list with all unique edges //In the half-edge data structure, we have for each edge an half edge going in each direction, //making it unneccessary to loop through all edges for intersection tests //The report suggest we should do a triangle walk, but it will not work if the mesh has holes List <HalfEdge2> uniqueEdges = triangleData.GetUniqueEdges(); //The steps numbering is from the report //Step 1. Loop over each constrained edge. For each of these edges, do steps 2-4 for (int i = 0; i < constraints.Count; i++) { //Let each constrained edge be defined by the vertices: MyVector2 c_p1 = constraints[i]; MyVector2 c_p2 = constraints[MathUtility.ClampListIndex(i + 1, constraints.Count)]; //Check if this constraint already exists in the triangulation, //if so we are happy and dont need to worry about this edge if (IsEdgeInListOfEdges(uniqueEdges, c_p1, c_p2)) { continue; } //Step 2. Find all edges in the current triangulation that intersects with this constraint Queue <HalfEdge2> intersectingEdges = FindIntersectingEdges_BruteForce(uniqueEdges, c_p1, c_p2); //Debug.Log("Intersecting edges: " + intersectingEdges.Count); //Step 3. Remove intersecting edges by flipping triangles List <HalfEdge2> newEdges = RemoveIntersectingEdges(c_p1, c_p2, intersectingEdges); //Step 4. Try to restore delaunay triangulation //Because we have constraints we will never get a delaunay triangulation RestoreDelaunayTriangulation(c_p1, c_p2, newEdges); } //Step 5. Remove superfluous triangles, such as the triangles "inside" the constraints if (shouldRemoveTriangles) { RemoveSuperfluousTriangles(triangleData, constraints); } return(triangleData); }
// // Remove all triangles that are inside the constraint // //This assumes the vertices in the constraint are ordered clockwise private static void RemoveSuperfluousTriangles(HalfEdgeData2 triangleData, List <MyVector2> constraints) { //This assumes we have at least 3 vertices in the constraint because we cant delete triangles inside a line if (constraints.Count < 3) { return; } HashSet <HalfEdgeFace2> trianglesToBeDeleted = FindTrianglesWithinConstraint(triangleData, constraints); //Delete the triangles foreach (HalfEdgeFace2 t in trianglesToBeDeleted) { HalfEdgeHelpMethods.DeleteTriangleFace(t, triangleData, true); } }
// // 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); }
// // 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); }
// // 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); }
// // Add the constraints to the delaunay triangulation // //timer is for debugging private static HalfEdgeData2 AddConstraints(HalfEdgeData2 triangleData, List <MyVector2> constraints, bool shouldRemoveTriangles, System.Diagnostics.Stopwatch timer = null) { //Validate the data if (constraints == null) { return(triangleData); } //Get a list with all edges //This is faster than first searching for unique edges //The report suggest we should do a triangle walk, but it will not work if the mesh has holes //The mesh has holes because we remove triangles while adding constraints one-by-one //so maybe better to remove triangles after we added all constraints... HashSet <HalfEdge2> edges = triangleData.edges; //The steps numbering is from the report //Step 1. Loop over each constrained edge. For each of these edges, do steps 2-4 for (int i = 0; i < constraints.Count; i++) { //Let each constrained edge be defined by the vertices: MyVector2 c_p1 = constraints[i]; MyVector2 c_p2 = constraints[MathUtility.ClampListIndex(i + 1, constraints.Count)]; //Check if this constraint already exists in the triangulation, //if so we are happy and dont need to worry about this edge //timer.Start(); if (IsEdgeInListOfEdges(edges, c_p1, c_p2)) { continue; } //timer.Stop(); //Step 2. Find all edges in the current triangulation that intersects with this constraint //Is returning unique edges only, so not one edge going in the opposite direction //timer.Start(); Queue <HalfEdge2> intersectingEdges = FindIntersectingEdges_BruteForce(edges, c_p1, c_p2); //timer.Stop(); //Debug.Log("Intersecting edges: " + intersectingEdges.Count); //Step 3. Remove intersecting edges by flipping triangles //This takes 0 seconds so is not bottleneck //timer.Start(); List <HalfEdge2> newEdges = RemoveIntersectingEdges(c_p1, c_p2, intersectingEdges); //timer.Stop(); //Step 4. Try to restore delaunay triangulation //Because we have constraints we will never get a delaunay triangulation //This takes 0 seconds so is not bottleneck //timer.Start(); RestoreDelaunayTriangulation(c_p1, c_p2, newEdges); //timer.Stop(); } //Step 5. Remove superfluous triangles, such as the triangles "inside" the constraints if (shouldRemoveTriangles) { //timer.Start(); RemoveSuperfluousTriangles(triangleData, constraints); //timer.Stop(); } return(triangleData); }
//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; } }
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); }
public static HalfEdgeData2 GenerateTriangulation(HashSet <MyVector2> points, List <MyVector2> hull, HashSet <List <MyVector2> > holes, bool shouldRemoveTriangles, HalfEdgeData2 triangleData) { //Start by generating a delaunay triangulation with all points, including the constraints HashSet <MyVector2> allPoints = new HashSet <MyVector2>(); if (points != null) { allPoints.UnionWith(points); } if (hull != null) { allPoints.UnionWith(hull); } if (holes != null) { foreach (List <MyVector2> hole in holes) { allPoints.UnionWith(hole); } } //Generate the Delaunay triangulation with some algorithm //triangleData = _Delaunay.FlippingEdges(allPoints); triangleData = _Delaunay.PointByPoint(allPoints, triangleData); //Modify the triangulation by adding the constraints to the delaunay triangulation triangleData = AddConstraints(triangleData, hull, shouldRemoveTriangles); foreach (List <MyVector2> hole in holes) { triangleData = AddConstraints(triangleData, hole, shouldRemoveTriangles); } //Debug.Log(triangleData.faces.Count); return(triangleData); }
// // Constrained Delaunay // //Algorithm 1. From the report "An algorithm for generating constrained delaunay triangulations" by Sloan //Start with a delaunay triangulation of all points, including the constraints //Then flip edges to make sure the constrains are in the triangulation //Then remove the unwanted triangles within the constraints (if we want to) // - sites: just some points // Constraints: // - hull: remove all triangles outside of the hull, should be ordered counter-clock-wise // - holes: remove all triangles within the holes, should be ordered clock-wise public static HalfEdgeData2 ConstrainedBySloan(HashSet <MyVector2> points, List <MyVector2> hull, HashSet <List <MyVector2> > holes, bool shouldRemoveTriangles, HalfEdgeData2 triangleData) { ConstrainedDelaunaySloan.GenerateTriangulation(points, hull, holes, shouldRemoveTriangles, triangleData); return(triangleData); }
// // Find which triangles are within a constraint // public static HashSet <HalfEdgeFace2> FindTrianglesWithinConstraint(HalfEdgeData2 triangleData, List <MyVector2> constraints) { HashSet <HalfEdgeFace2> trianglesToDelete = new HashSet <HalfEdgeFace2>(); //Store the triangles we flood fill in this queue Queue <HalfEdgeFace2> trianglesToCheck = new Queue <HalfEdgeFace2>(); //Step 1. Find all half-edges in the current triangulation which are constraint //Maybe faster to find all constraintEdges for ALL constraints because we are doing this per hole and hull //We have to find ALL because some triangles are not connected and will thus be missed if we find just a single start-triangle //Is also needed when flood-filling so we dont jump over a constraint HashSet <HalfEdge2> constraintEdges = FindAllConstraintEdges(constraints, triangleData); //Each edge is associated with a face which should be deleted foreach (HalfEdge2 e in constraintEdges) { if (!trianglesToCheck.Contains(e.face)) { trianglesToCheck.Enqueue(e.face); } } //Step 2. Find the rest of the triangles within the constraint by using a flood-fill algorithm int safety = 0; List <HalfEdge2> edgesToCheck = new List <HalfEdge2>(); 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(); //Add it for deletion trianglesToDelete.Add(t); //Investigate the triangles on the opposite sides of these edges edgesToCheck.Clear(); edgesToCheck.Add(t.edge); edgesToCheck.Add(t.edge.nextEdge); edgesToCheck.Add(t.edge.nextEdge.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 foreach (HalfEdge2 e in edgesToCheck) { //No neighbor exists if (e.oppositeEdge == null) { continue; } HalfEdgeFace2 neighbor = e.oppositeEdge.face; //We have already visited this neighbor if (trianglesToDelete.Contains(neighbor) || trianglesToCheck.Contains(neighbor)) { continue; } //This edge is a constraint and we can't jump across constraints if (constraintEdges.Contains(e)) { continue; } trianglesToCheck.Enqueue(neighbor); } } return(trianglesToDelete); }
//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); }
public static HalfEdgeData2 GenerateTriangulation(HashSet <MyVector2> points, List <MyVector2> constraints, bool shouldRemoveTriangles, HalfEdgeData2 triangleData) { //Start by generating a delaunay triangulation with all points, including the constraints if (constraints != null) { points.UnionWith(constraints); } //Generate the Delaunay triangulation with some algorithm //triangleData = _Delaunay.FlippingEdges(points); triangleData = _Delaunay.PointByPoint(points, triangleData); //Modify the triangulation by adding the constraints to the delaunay triangulation if (constraints != null) { triangleData = AddConstraints(triangleData, constraints, shouldRemoveTriangles); } //Debug.Log(triangleData.faces.Count); return(triangleData); }
// // 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); }
// // Delaunay // //Algorithm 1. Triangulate the points with some algorithm - then flip edges until we have a delaunay triangulation public static HalfEdgeData2 FlippingEdges(HashSet <MyVector2> points, HalfEdgeData2 triangleData) { triangleData = DelaunayFlipEdges.GenerateTriangulation(points, triangleData); return(triangleData); }
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); }
//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 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); }
// // 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 static HalfEdgeData2 GenerateTriangulation(HashSet <MyVector2> points, HalfEdgeData2 triangleData) { //Step 1. Triangulate the points with some algorithm. The result is a convex triangulation //List<Triangle> triangles = TriangulatePoints.IncrementalTriangulation(points); HashSet <Triangle2> triangles = _TriangulatePoints.TriangleSplitting(points); //Step 2. Change the data structure from triangle to half-edge to make it easier to flip edges triangleData = _TransformBetweenDataStructures.Triangle2ToHalfEdge2(triangles, triangleData); //Step 3. Flip edges until we have a delaunay triangulation FlipEdges(triangleData); return(triangleData); }
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 HalfEdgeData2 GenerateTriangulation(HashSet <MyVector2> points, List <MyVector2> hull, HashSet <List <MyVector2> > holes, bool shouldRemoveTriangles, HalfEdgeData2 triangleData) { //Start by generating a delaunay triangulation with all points, including the constraints HashSet <MyVector2> allPoints = new HashSet <MyVector2>(); if (points != null) { allPoints.UnionWith(points); } if (hull != null) { allPoints.UnionWith(hull); } if (holes != null) { foreach (List <MyVector2> hole in holes) { allPoints.UnionWith(hole); } } System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch(); //Generate the Delaunay triangulation with some algorithm //timer.Start(); //triangleData = _Delaunay.FlippingEdges(allPoints); triangleData = _Delaunay.PointByPoint(allPoints, triangleData); //timer.Stop(); //Delaunay takes 0.003 seconds for the house so is not the bottle neck //Debug.Log($"Delaunay triangulation took {timer.ElapsedMilliseconds / 1000f} seconds"); //Modify the triangulation by adding the constraints to the delaunay triangulation triangleData = AddConstraints(triangleData, hull, shouldRemoveTriangles, timer); foreach (List <MyVector2> hole in holes) { triangleData = AddConstraints(triangleData, hole, shouldRemoveTriangles, timer); } //Debug.Log(triangleData.faces.Count); //Debug.Log($"Whatever time we measured it took {timer.ElapsedMilliseconds / 1000f} seconds"); return(triangleData); }
// // Find which triangles are within a constraint // public static HashSet <HalfEdgeFace2> FindTrianglesWithinConstraint(HalfEdgeData2 triangleData, List <MyVector2> constraints) { HashSet <HalfEdgeFace2> trianglesToDelete = new HashSet <HalfEdgeFace2>(); //Store the triangles we flood fill in this queue Queue <HalfEdgeFace2> trianglesToCheck = new Queue <HalfEdgeFace2>(); //Step 1. Find all triangles with an edge that is a constraint //We have to find all because they are not always connected, so we cant just find one and flood fill from it //for (int i = 0; i < constraints.Count; i++) //{ // MyVector2 c_p1 = constraints[i]; // MyVector2 c_p2 = constraints[MathUtility.ClampListIndex(i + 1, constraints.Count)]; // HalfEdgeFace2 borderTriangle = FindTriangleWithEdge(c_p1, c_p2, triangleData); // //Maybe this edge has not triangle, which can happen if it's on the border // if (borderTriangle != null) // { // trianglesToCheck.Enqueue(borderTriangle); // } //} HashSet <HalfEdgeFace2> borderFaces = FindAllTrianglesBorderingTheConstraint(constraints, triangleData); foreach (HalfEdgeFace2 f in borderFaces) { trianglesToCheck.Enqueue(f); } //Step 2. Find the rest of the triangles within the constraint by using a flood fill algorithm int safety = 0; List <HalfEdge2> edgesToCheck = new List <HalfEdge2>(); 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(); //Add it for deletion trianglesToDelete.Add(t); //Investigate the triangles on the opposite sides of these edges edgesToCheck.Clear(); edgesToCheck.Add(t.edge); edgesToCheck.Add(t.edge.nextEdge); edgesToCheck.Add(t.edge.nextEdge.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 foreach (HalfEdge2 e in edgesToCheck) { //No neighbor exists if (e.oppositeEdge == null) { continue; } HalfEdgeFace2 neighbor = e.oppositeEdge.face; //We have already visited this neighbor if (trianglesToDelete.Contains(neighbor) || trianglesToCheck.Contains(neighbor)) { continue; } //We have to check if this edge is a constraint because it might be alone on the border without neighbors that are also constraints MyVector2 p1 = e.prevEdge.v.position; MyVector2 p2 = e.v.position; if (IsEdgeAConstraint(p1, p2, constraints)) { continue; } trianglesToCheck.Enqueue(neighbor); } } return(trianglesToDelete); }
//Algorithm 2. Start with one triangle covering all points - then insert the points one-by-one while flipping edges //From the report "A fast algorithm for constructing Delaunay triangulations in the plane" by Sloan public static HalfEdgeData2 PointByPoint(HashSet <MyVector2> points, HalfEdgeData2 triangleData) { triangleData = DelaunayIncrementalSloan.GenerateTriangulation(points, triangleData); return(triangleData); }