// // Remove all triangles that are inside the constraint // //This assumes the vertices in the constraint are ordered clockwise private IEnumerator RemoveSuperfluousTriangles(HalfEdgeData2 triangleData, List <MyVector2> constraints, Normalizer2 normalizer) { //This assumes we have at least 3 vertices in the constraint because we cant delete triangles inside a line if (constraints.Count < 3) { yield return(null); } HashSet <HalfEdgeFace2> trianglesToBeDeleted = FindTrianglesWithinConstraint(triangleData, constraints); if (trianglesToBeDeleted == null) { Debug.Log("There are no triangles to delete"); yield return(null); } //Delete the triangles foreach (HalfEdgeFace2 t in trianglesToBeDeleted) { HalfEdgeHelpMethods.DeleteTriangleFace(t, triangleData, true); // // PAUSE AND VISUALIZE // visualizeController.DisplayMeshMain(triangleData, normalizer); yield return(new WaitForSeconds(0.5f)); } }
public void DisplayMeshMain(HalfEdgeData2 meshData, Normalizer2 normalizer) { //UnNormalize and to 3d HalfEdgeData3 meshDataUnNormalized_3d = new HalfEdgeData3(); //We dont want to modify the original data //HalfEdgeData2 meshDataUnNormalized = normalizer.UnNormalize(meshData); HashSet <HalfEdgeFace2> faces_2d = meshData.faces; foreach (HalfEdgeFace2 f in faces_2d) { MyVector2 p1 = f.edge.v.position; MyVector2 p2 = f.edge.nextEdge.v.position; MyVector2 p3 = f.edge.nextEdge.nextEdge.v.position; p1 = normalizer.UnNormalize(p1); p2 = normalizer.UnNormalize(p2); p3 = normalizer.UnNormalize(p3); meshDataUnNormalized_3d.AddTriangle(p1.ToMyVector3_Yis3D(), p2.ToMyVector3_Yis3D(), p3.ToMyVector3_Yis3D()); } this.meshData = meshDataUnNormalized_3d.faces; DisplayMesh(meshDataUnNormalized_3d.faces, displayMeshHere); //Normalize again //meshData = normalizer.Normalize(meshDataUnNormalized); }
//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 }
//Generate the mesh from the half-edge data structure, which is called when we have flipped an edge public void GenerateMesh(HalfEdgeData2 triangleData_normalized) { //From half-edge to triangle while unnormalizing HashSet <Triangle2> triangles_2d = new HashSet <Triangle2>(); foreach (HalfEdgeFace2 f in triangleData_normalized.faces) { //Each face has in this case three edges MyVector2 p1 = f.edge.v.position; MyVector2 p2 = f.edge.nextEdge.v.position; MyVector2 p3 = f.edge.nextEdge.nextEdge.v.position; //Unnormalize the point p1 = HelpMethods.UnNormalize(p1, normalizingBox, dMax); p2 = HelpMethods.UnNormalize(p2, normalizingBox, dMax); p3 = HelpMethods.UnNormalize(p3, normalizingBox, dMax); Triangle2 t = new Triangle2(p1, p2, p3); triangles_2d.Add(t); } //Make sure the triangles have the correct orientation //triangles_2d = HelpMethods.OrientTrianglesClockwise(triangles_2d); //From 2d to mesh in one step //Mesh displayMesh = _TransformBetweenDataStructures.Triangles2ToMesh(triangles_2d, useCompressedMesh: false); //Generate the triangle meshes GenerateTriangleMeshes(triangles_2d); }
// // Visualz // //Show triangles private void ShowTriangles(HalfEdgeData2 triangles) { controller.ResetMultiColoredMeshes(); List <Mesh> meshes = controller.GenerateTriangulationMesh(triangles, shouldUnNormalize: true); List <Material> materials = controller.GenerateRandomMaterials(meshes.Count); controller.multiColoredMeshes = meshes; controller.multiColoredMeshesMaterials = materials; }
private void GenerateDelaunay(HashSet <MyVector2> points_2d) { //Normalize AABB2 normalizingBox = new AABB2(new List <MyVector2>(points_2d)); float dMax = HelpMethods.CalculateDMax(normalizingBox); HashSet <MyVector2> points_2d_normalized = HelpMethods.Normalize(points_2d, normalizingBox, dMax); //Generate delaunay //HalfEdgeData2 delaunayData = _Delaunay.FlippingEdges(points_2d_normalized, new HalfEdgeData2()); HalfEdgeData2 delaunayData = _Delaunay.PointByPoint(points_2d_normalized, new HalfEdgeData2()); //UnNormalize HalfEdgeData2 triangleData = HelpMethods.UnNormalize(delaunayData, normalizingBox, dMax); //From halfedge to triangle HashSet <Triangle2> triangles = _TransformBetweenDataStructures.HalfEdge2ToTriangle2(triangleData); //Make sure they have the correct orientation triangles = HelpMethods.OrientTrianglesClockwise(triangles); //2d to 3d HashSet <Triangle3> triangles_3d = new HashSet <Triangle3>(); int counter = -1; foreach (Triangle2 t in triangles) { counter++; //if (counter != 2) //{ // continue; //} triangles_3d.Add(new Triangle3(t.p1.ToMyVector3_Yis3D(), t.p2.ToMyVector3_Yis3D(), t.p3.ToMyVector3_Yis3D())); //Debug.Log($"p1: {t.p1.x} {t.p1.y} p2: {t.p2.x} {t.p2.y} p3: {t.p3.x} {t.p3.y}"); //MyVector2 circleCenter = _Geometry.CalculateCircleCenter(t.p1, t.p2, t.p3); //Debug.Log("Circle center: " + circleCenter.x + " " + circleCenter.y); } Mesh delaunayMesh = _TransformBetweenDataStructures.Triangle3ToCompressedMesh(triangles_3d); //Display the delaunay triangles TestAlgorithmsHelpMethods.DisplayMeshEdges(delaunayMesh, Color.black); }
public void Delaunay_FlipEdges_Visualizer(HashSet <MyVector2> points, HalfEdgeData2 triangleData) { controller = GetComponent <DelaunayVisualizerController>(); //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 StartCoroutine(FlipEdges(triangleData)); }
//Remove the supertriangle IEnumerator 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); //VISUALZ ShowTriangles(triangulationData); yield return(new WaitForSeconds(controller.pauseTime)); } //VISUALZ - show the colored mesh when its finished controller.shouldDisplayColoredMesh = true; yield return(null); }
public void StartVisualizer(HashSet <MyVector2> points, HalfEdgeData2 triangleData) { controller = GetComponent <VisualizerController>(); //Step 1. Triangulate the points with some algorithm. The result is a convex triangulation HashSet <Triangle2> triangles = _TriangulatePoints.VisibleEdgesTriangulation(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); //Generate the visual triangles controller.GenerateTriangulationMesh(triangleData); //Step 3. Flip edges until we have a delaunay triangulation StartCoroutine(FlipEdges(triangleData)); }
//Generate the mesh from the half-edge data structure, which is called when we have flipped an edge public void GenerateTriangulationMesh(HalfEdgeData2 triangleData_normalized) { //From half-edge to triangle HashSet <Triangle2> triangles_2d = new HashSet <Triangle2>(); foreach (HalfEdgeFace2 f in triangleData_normalized.faces) { //Each face has in this case three edges MyVector2 p1 = f.edge.v.position; MyVector2 p2 = f.edge.nextEdge.v.position; MyVector2 p3 = f.edge.nextEdge.nextEdge.v.position; Triangle2 t = new Triangle2(p1, p2, p3); triangles_2d.Add(t); } GenerateTriangulationMesh(triangles_2d); }
// // Generate meshes // //Generate list of meshes from the Half-edge data structure public List <Mesh> GenerateTriangulationMesh(HalfEdgeData2 triangleData, bool shouldUnNormalize) { //From half-edge to triangle HashSet <Triangle2> triangles_2d = new HashSet <Triangle2>(); foreach (HalfEdgeFace2 f in triangleData.faces) { //Each face has in this case three edges MyVector2 p1 = f.edge.v.position; MyVector2 p2 = f.edge.nextEdge.v.position; MyVector2 p3 = f.edge.nextEdge.nextEdge.v.position; Triangle2 t = new Triangle2(p1, p2, p3); triangles_2d.Add(t); } List <Mesh> meshes = GenerateTriangulationMesh(triangles_2d, shouldUnNormalize); return(meshes); }
public void StartVisualizer(HashSet <MyVector2> points, HalfEdgeData2 triangulationData) { controller = GetComponent <VisualizerController>(); //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) 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); //Start the visualization StartCoroutine(InsertPoints(points, triangulationData, superTriangle)); }
private void GenerateDelaunay(HashSet <MyVector2> points) { HalfEdgeData2 delaunayData = _Delaunay.FlippingEdges(points, new HalfEdgeData2()); //From halfedge to triangle HashSet <Triangle2> triangles = _TransformBetweenDataStructures.HalfEdge2ToTriangle2(delaunayData); //Make sure they have the correct orientation triangles = HelpMethods.OrientTrianglesClockwise(triangles); //2d to 3d HashSet <Triangle3> triangles_3d = new HashSet <Triangle3>(); foreach (Triangle2 t in triangles) { triangles_3d.Add(new Triangle3(t.p1.ToMyVector3(), t.p2.ToMyVector3(), t.p3.ToMyVector3())); } Mesh delaunayMesh = _TransformBetweenDataStructures.Triangle3ToCompressedMesh(triangles_3d); //Display the delaunay triangles TestAlgorithmsHelpMethods.DisplayMeshEdges(delaunayMesh, Color.black); }
private void GenerateDelaunay(HashSet <MyVector2> points_2d) { //Normalize AABB2 normalizingBox = new AABB2(new List <MyVector2>(points_2d)); float dMax = HelpMethods.CalculateDMax(normalizingBox); HashSet <MyVector2> points_2d_normalized = HelpMethods.Normalize(points_2d, normalizingBox, dMax); //Generate delaunay //HalfEdgeData2 delaunayData = _Delaunay.FlippingEdges(points_2d_normalized, new HalfEdgeData2()); HalfEdgeData2 delaunayData = _Delaunay.PointByPoint(points_2d_normalized, new HalfEdgeData2()); //UnNormalize HalfEdgeData2 triangleData = HelpMethods.UnNormalize(delaunayData, normalizingBox, dMax); //From halfedge to triangle HashSet <Triangle2> triangles = _TransformBetweenDataStructures.HalfEdge2ToTriangle2(triangleData); //Make sure they have the correct orientation triangles = HelpMethods.OrientTrianglesClockwise(triangles); //2d to 3d HashSet <Triangle3> triangles_3d = new HashSet <Triangle3>(); foreach (Triangle2 t in triangles) { triangles_3d.Add(new Triangle3(t.p1.ToMyVector3(), t.p2.ToMyVector3(), t.p3.ToMyVector3())); } Mesh delaunayMesh = _TransformBetweenDataStructures.Triangle3ToCompressedMesh(triangles_3d); //Display the delaunay triangles TestAlgorithmsHelpMethods.DisplayMeshEdges(delaunayMesh, Color.black); }
private IEnumerator GenerateConstrainedDelaunayLoop(HashSet <MyVector2> points, List <MyVector2> hull, HashSet <List <MyVector2> > holes, bool shouldRemoveTriangles, HalfEdgeData2 triangleData, Normalizer2 normalizer) { //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); // // PAUSE AND VISUALIZE // visualizeController.DisplayMeshMain(triangleData, normalizer); yield return(new WaitForSeconds(3f)); //Modify the triangulation by adding the constraints to the delaunay triangulation yield return(StartCoroutine(AddConstraints(triangleData, hull, shouldRemoveTriangles, normalizer))); foreach (List <MyVector2> hole in holes) { yield return(StartCoroutine(AddConstraints(triangleData, hole, shouldRemoveTriangles, normalizer))); } }
//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 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); }
// // 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 IEnumerator RemoveIntersectingEdges(MyVector2 v_i, MyVector2 v_j, Queue <HalfEdge2> intersectingEdges, List <HalfEdge2> newEdges, HalfEdgeData2 triangleData, Normalizer2 normalizer) { 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); // // PAUSE AND VISUALIZE // visualizeController.DisplayMeshMain(triangleData, normalizer); yield return(new WaitForSeconds(0.5f)); //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); } } } }
public void GenerateTriangulation() { //Get the random points //HashSet<Vector3> randomPoints = TestAlgorithmsHelpMethods.GenerateRandomPoints(seed, halfMapSize, numberOfPoints); //From 3d to 2d //HashSet<MyVector2> randomPoints_2d = new HashSet<MyVector2>(randomPoints.Select(x => x.ToMyVector2())); /* * List<MyVector2> constraints_2d = constraints.Select(x => x.ToMyVector2()).ToList(); * * //Normalize to range 0-1 * //We should use all points, including the constraints because the hole may be outside of the random points * List<MyVector2> allPoints = new List<MyVector2>(); * * allPoints.AddRange(new List<MyVector2>(points_2d)); * allPoints.AddRange(constraints_2d); * * AABB2 normalizingBox = new AABB2(new List<MyVector2>(points_2d)); * * float dMax = HelpMethods.CalculateDMax(normalizingBox); * * HashSet<MyVector2> points_2d_normalized = HelpMethods.Normalize(points_2d, normalizingBox, dMax); * * List<MyVector2> constraints_2d_normalized = HelpMethods.Normalize(constraints_2d, normalizingBox, dMax); */ //Hull List <Vector3> hullPoints = TestAlgorithmsHelpMethods.GetPointsFromParent(hullConstraintParent); List <MyVector2> hullPoints_2d = hullPoints.Select(x => x.ToMyVector2()).ToList();; //Holes HashSet <List <MyVector2> > allHolePoints_2d = new HashSet <List <MyVector2> >(); foreach (Transform holeParent in holeConstraintParents) { List <Vector3> holePoints = TestAlgorithmsHelpMethods.GetPointsFromParent(holeParent); if (holePoints != null) { List <MyVector2> holePoints_2d = holePoints.Select(x => x.ToMyVector2()).ToList(); allHolePoints_2d.Add(holePoints_2d); } } //Normalize to range 0-1 //We should use all points, including the constraints because the hole may be outside of the random points List <MyVector2> allPoints = new List <MyVector2>(); //allPoints.AddRange(randomPoints_2d); allPoints.AddRange(hullPoints_2d); foreach (List <MyVector2> hole in allHolePoints_2d) { allPoints.AddRange(hole); } AABB2 normalizingBox = new AABB2(allPoints); float dMax = HelpMethods.CalculateDMax(normalizingBox); List <MyVector2> hullPoints_2d_normalized = HelpMethods.Normalize(hullPoints_2d, normalizingBox, dMax); HashSet <List <MyVector2> > allHolePoints_2d_normalized = new HashSet <List <MyVector2> >(); foreach (List <MyVector2> hole in allHolePoints_2d) { List <MyVector2> hole_normalized = HelpMethods.Normalize(hole, normalizingBox, dMax); allHolePoints_2d_normalized.Add(hole_normalized); } // // Generate the triangulation // //Algorithm 1. Delaunay by triangulate all points with some bad algorithm and then flip edges until we get a delaunay triangulation //HalfEdgeData2 triangleData_normalized = _Delaunay.FlippingEdges(points_2d_normalized, new HalfEdgeData2()); //Algorithm 2. Delaunay by inserting point-by-point while flipping edges after inserting a single point //HalfEdgeData2 triangleData_normalized = _Delaunay.PointByPoint(points_2d_normalized, new HalfEdgeData2()); //Algorithm 3. Constrained delaunay HalfEdgeData2 triangleData_normalized = _Delaunay.ConstrainedBySloan(null, hullPoints_2d_normalized, allHolePoints_2d_normalized, shouldRemoveTriangles: true, new HalfEdgeData2()); //UnNormalize HalfEdgeData2 triangleData = HelpMethods.UnNormalize(triangleData_normalized, normalizingBox, dMax); //From half-edge to triangle HashSet <Triangle2> triangles_2d = _TransformBetweenDataStructures.HalfEdge2ToTriangle2(triangleData); //From triangulation to mesh //Make sure the triangles have the correct orientation triangles_2d = HelpMethods.OrientTrianglesClockwise(triangles_2d); //From 2d to 3d HashSet <Triangle3> triangles_3d = new HashSet <Triangle3>(); foreach (Triangle2 t in triangles_2d) { triangles_3d.Add(new Triangle3(t.p1.ToMyVector3_Yis3D(), t.p2.ToMyVector3_Yis3D(), t.p3.ToMyVector3_Yis3D())); } triangulatedMesh = _TransformBetweenDataStructures.Triangle3ToCompressedMesh(triangles_3d); }
IEnumerator InsertPoints(HashSet <MyVector2> points, HalfEdgeData2 triangulationData, Triangle2 superTriangle) { //VISUALZ ShowTriangles(triangulationData); //VISUALZ - dont show the colored mesh until its finished because its flickering controller.shouldDisplayColoredMesh = false; yield return(new WaitForSeconds(controller.pauseTime)); //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. 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); //VISUALZ //Display the point as a black circle ShowCircle(p); yield return(new WaitForSeconds(controller.pauseTime)); ShowTriangles(triangulationData); yield return(new WaitForSeconds(controller.pauseTime)); //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; //VISUALZ controller.flipText.text = "Flipped edges: " + flippedEdges; ShowTriangles(triangulationData); yield return(new WaitForSeconds(controller.pauseTime)); } } } //Dont show the last point we added controller.ResetBlackMeshes(); //Step 8. Delete the vertices belonging to the supertriangle StartCoroutine(RemoveSuperTriangle(superTriangle, triangulationData)); yield return(null); }
public void GenererateTriangulation() { //Get the random points HashSet <Vector3> points = TestAlgorithmsHelpMethods.GenerateRandomPoints(seed, halfMapSize, numberOfPoints); //From 3d to 2d HashSet <MyVector2> points_2d = new HashSet <MyVector2>(); foreach (Vector3 v in points) { points_2d.Add(v.ToMyVector2()); } List <MyVector2> constraints_2d = new List <MyVector2>(); foreach (Vector3 v in constraints) { constraints_2d.Add(v.ToMyVector2()); } //Normalize to range 0-1 //We should use all points, including the constraints List <MyVector2> allPoints = new List <MyVector2>(); allPoints.AddRange(new List <MyVector2>(points_2d)); allPoints.AddRange(constraints_2d); AABB2 normalizingBox = new AABB2(new List <MyVector2>(points_2d)); float dMax = HelpMethods.CalculateDMax(normalizingBox); HashSet <MyVector2> points_2d_normalized = HelpMethods.Normalize(points_2d, normalizingBox, dMax); List <MyVector2> constraints_2d_normalized = HelpMethods.Normalize(constraints_2d, normalizingBox, dMax); // // Generate the triangulation // //Algorithm 1. Delaunay by triangulate all points with some bad algorithm and then flip edges until we get a delaunay triangulation //HalfEdgeData2 triangleData_normalized = _Delaunay.FlippingEdges(points_2d_normalized, new HalfEdgeData2()); //Algorithm 2. Delaunay by inserting point-by-point while flipping edges after inserting a single point //HalfEdgeData2 triangleData_normalized = _Delaunay.PointByPoint(points_2d_normalized, new HalfEdgeData2()); //Algorithm 3. Constrained delaunay HalfEdgeData2 triangleData_normalized = _Delaunay.ConstrainedBySloan(points_2d_normalized, constraints_2d_normalized, false, new HalfEdgeData2()); //UnNormalize HalfEdgeData2 triangleData = HelpMethods.UnNormalize(triangleData_normalized, normalizingBox, dMax); //From half-edge to triangle HashSet <Triangle2> triangles_2d = _TransformBetweenDataStructures.HalfEdge2ToTriangle2(triangleData); //From triangulation to mesh //Make sure the triangles have the correct orientation triangles_2d = HelpMethods.OrientTrianglesClockwise(triangles_2d); //From 2d to 3d HashSet <Triangle3> triangles_3d = new HashSet <Triangle3>(); foreach (Triangle2 t in triangles_2d) { triangles_3d.Add(new Triangle3(t.p1.ToMyVector3(), t.p2.ToMyVector3(), t.p3.ToMyVector3())); } triangulatedMesh = _TransformBetweenDataStructures.Triangle3ToCompressedMesh(triangles_3d); }
// // 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 IEnumerator RestoreDelaunayTriangulation(MyVector2 c_p1, MyVector2 c_p2, List <HalfEdge2> newEdges, HalfEdgeData2 triangleData, Normalizer2 normalizer) { 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; // // PAUSE AND VISUALIZE // visualizeController.DisplayMeshMain(triangleData, normalizer); yield return(new WaitForSeconds(0.5f)); } } //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; } } }
//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; } }
//Flip edges until we get a delaunay triangulation private IEnumerator 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; //If we want to display the test circle //controller.GenerateDelaunayCircleMeshes(a, b, c, d); //yield return new WaitForSeconds(controller.pauseTime); //Test if we should flip this edge if (DelaunayMethods.ShouldFlipEdge(a, b, c, d)) { flippedEdges += 1; hasFlippedEdge = true; HalfEdgeHelpMethods.FlipTriangleEdge(thisEdge); controller.flipText.text = "Flipped edges: " + flippedEdges; controller.GenerateTriangulationMesh(triangleData); yield return(new WaitForSeconds(controller.pauseTime)); } } //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 circle meshes so we see that we are finished controller.ClearBlackMeshes(); yield return(null); }
// // Add the constraints to the delaunay triangulation // //timer is for debugging private IEnumerator AddConstraints(HalfEdgeData2 triangleData, List <MyVector2> constraints, bool shouldRemoveTriangles, Normalizer2 normalizer, System.Diagnostics.Stopwatch timer = null) { //Validate the data if (constraints == null) { yield return(null); } // // PAUSE AND VISUALIZE // //Show the constraint with a line mesh HashSet <Triangle2> lineTriangles = _GenerateMesh.ConnectedLineSegments(constraints, width: 0.01f, isConnected: true); //UnNormalized and to half-edge 3 (also move each vertex up a little or will intersect with the underlying mesh) HalfEdgeData3 lineData = new HalfEdgeData3(); foreach (Triangle2 t in lineTriangles) { MyVector2 p1 = t.p1; MyVector2 p2 = t.p2; MyVector2 p3 = t.p3; p1 = normalizer.UnNormalize(p1); p2 = normalizer.UnNormalize(p2); p3 = normalizer.UnNormalize(p3); lineData.AddTriangle(p1.ToMyVector3_Yis3D(0.1f), p2.ToMyVector3_Yis3D(0.1f), p3.ToMyVector3_Yis3D(0.1f)); } visualizeController.DisplayMeshOtherUnNormalized(lineData.faces); yield return(new WaitForSeconds(2f)); //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 = new List <HalfEdge2>(); yield return(StartCoroutine(RemoveIntersectingEdges(c_p1, c_p2, intersectingEdges, newEdges, triangleData, normalizer))); //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(); yield return(StartCoroutine(RestoreDelaunayTriangulation(c_p1, c_p2, newEdges, triangleData, normalizer))); //timer.Stop(); } //Step 5. Remove superfluous triangles, such as the triangles "inside" the constraints if (shouldRemoveTriangles) { //timer.Start(); yield return(StartCoroutine(RemoveSuperfluousTriangles(triangleData, constraints, normalizer))); //timer.Stop(); } //return triangleData; // // PAUSE AND VISUALIZE // visualizeController.HideMeshOther(); yield return(new WaitForSeconds(2f)); }