//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); }
//The original algorithm calculates the intersection between two polygons, this will instead get the outside //Assumes the polygons are oriented counter clockwise //poly is the polygon we want to cut //Assumes the polygon we want to remove from the other polygon is convex, so clipPolygon has to be convex //We will end up with the !intersection of the polygons public static List <List <MyVector2> > ClipPolygonInverted(List <MyVector2> poly, List <Plane2> clippingPlanes) { //The result may be more than one polygons List <List <MyVector2> > finalPolygons = new List <List <MyVector2> >(); List <MyVector2> vertices = new List <MyVector2>(poly); //The remaining polygon after each cut List <MyVector2> vertices_tmp = new List <MyVector2>(); //Clip the polygon for (int i = 0; i < clippingPlanes.Count; i++) { Plane2 plane = clippingPlanes[i]; //A new polygon which is the part of the polygon which is outside of this plane List <MyVector2> outsidePolygon = new List <MyVector2>(); for (int j = 0; j < vertices.Count; j++) { int jPlusOne = MathUtility.ClampListIndex(j + 1, vertices.Count); MyVector2 v1 = vertices[j]; MyVector2 v2 = vertices[jPlusOne]; //Calculate the distance to the plane from each vertex //This is how we will know if they are inside or outside //If they are inside, the distance is positive, which is why the planes normals have to be oriented to the inside float dist_to_v1 = _Geometry.GetSignedDistanceFromPointToPlane(v1, plane); float dist_to_v2 = _Geometry.GetSignedDistanceFromPointToPlane(v2, plane); //TODO: What will happen if they are exactly 0? //Case 1. Both are inside (= to the left), save v2 to the other polygon if (dist_to_v1 >= 0f && dist_to_v2 >= 0f) { vertices_tmp.Add(v2); } //Case 2. Both are outside (= to the right), save v1 else if (dist_to_v1 < 0f && dist_to_v2 < 0f) { outsidePolygon.Add(v2); } //Case 3. Outside -> Inside, save intersection point else if (dist_to_v1 < 0f && dist_to_v2 >= 0f) { MyVector2 rayDir = MyVector2.Normalize(v2 - v1); Ray2 ray = new Ray2(v1, rayDir); MyVector2 intersectionPoint = _Intersections.GetRayPlaneIntersectionPoint(plane, ray); outsidePolygon.Add(intersectionPoint); vertices_tmp.Add(intersectionPoint); vertices_tmp.Add(v2); } //Case 4. Inside -> Outside, save intersection point and v2 else if (dist_to_v1 >= 0f && dist_to_v2 < 0f) { MyVector2 rayDir = MyVector2.Normalize(v2 - v1); Ray2 ray = new Ray2(v1, rayDir); MyVector2 intersectionPoint = _Intersections.GetRayPlaneIntersectionPoint(plane, ray); outsidePolygon.Add(intersectionPoint); outsidePolygon.Add(v2); vertices_tmp.Add(intersectionPoint); } } //Add the polygon outside of this plane to the list of all polygons that are outside of all planes if (outsidePolygon.Count > 0) { finalPolygons.Add(outsidePolygon); } //Add the polygon which was inside of this and previous planes to the polygon we want to test vertices.Clear(); vertices.AddRange(vertices_tmp); vertices_tmp.Clear(); } return(finalPolygons); }
// // Algorithm 1 // //If you have points on a convex hull, sorted one after each other //If you have colinear points, it will ignore some of them but still triangulate the entire area //Colinear points are not changing the shape public static HashSet <Triangle2> GetTriangles(List <MyVector2> pointsOnHull, bool addColinearPoints) { //If we hadnt have to deal with colinear points, this algorithm would be really simple: HashSet <Triangle2> triangles = new HashSet <Triangle2>(); //This vertex will be a vertex in all triangles MyVector2 a = pointsOnHull[0]; //And then we just loop through the other edges to make all triangles for (int i = 1; i < pointsOnHull.Count; i++) { MyVector2 b = pointsOnHull[i]; MyVector2 c = pointsOnHull[MathUtility.ClampListIndex(i + 1, pointsOnHull.Count)]; //Is this a valid triangle? //If a, b, c are on the same line, the triangle has no area and we can't add it LeftOnRight pointRelation = Geometry.IsPoint_Left_On_Right_OfVector(a, b, c); if (pointRelation == LeftOnRight.On) { continue; } triangles.Add(new Triangle2(a, b, c)); } //Add the missing colinear points by splitting triangles //Step 2.1. We have now triangulated the entire convex polygon, but if we have colinear points //some of those points were not added //We can add them by splitting triangles //Find colinear points if (addColinearPoints) { HashSet <MyVector2> colinearPoints = new HashSet <MyVector2>(pointsOnHull); //Remove points that are in the triangulation from the points on the convex hull //and we can see which where not added = the colinear points foreach (Triangle2 t in triangles) { colinearPoints.Remove(t.p1); colinearPoints.Remove(t.p2); colinearPoints.Remove(t.p3); } //Debug.Log("Colinear points: " + colinearPoints.Count); //Go through all colinear points and find which edge they should split //On the border we only need to split one edge because this edge has no neighbors foreach (MyVector2 p in colinearPoints) { foreach (Triangle2 t in triangles) { //Is this point in the triangle if (Intersections.PointTriangle(t, p, includeBorder: true)) { SplitTriangleEdge(t, p, triangles); break; } } } } return(triangles); }
// // 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); }
// // Connected line segments // //isConnected means if the end points are connected to form a loop public static HashSet <Triangle2> ConnectedLineSegments(List <MyVector2> points, float width, bool isConnected) { if (points != null && points.Count < 2) { Debug.Log("Cant form a line with fewer than two points"); return(null); } //Generate the triangles HashSet <Triangle2> lineTriangles = new HashSet <Triangle2>(); //If the lines are connected we need to do plane-plane intersection to find the //coordinate where the lines meet at each point, or the line segments will //not get the same size //(There might be a better way to do it than with plane-plane intersection) List <MyVector2> topCoordinate = new List <MyVector2>(); List <MyVector2> bottomCoordinate = new List <MyVector2>(); float halfWidth = width * 0.5f; for (int i = 0; i < points.Count; i++) { MyVector2 p = points[i]; //First point = special case if the lines are not connected if (i == 0 && !isConnected) { MyVector2 lineDir = points[1] - points[0]; MyVector2 lineNormal = MyVector2.Normalize(new MyVector2(lineDir.y, -lineDir.x)); topCoordinate.Add(p + lineNormal * halfWidth); bottomCoordinate.Add(p - lineNormal * halfWidth); } //Last point = special case if the lines are not connected else if (i == points.Count - 1 && !isConnected) { MyVector2 lineDir = p - points[points.Count - 2]; MyVector2 lineNormal = MyVector2.Normalize(new MyVector2(lineDir.y, -lineDir.x)); topCoordinate.Add(p + lineNormal * halfWidth); bottomCoordinate.Add(p - lineNormal * halfWidth); } else { //Now we need to find the intersection points between the top line and the bottom line MyVector2 p_before = points[MathUtility.ClampListIndex(i - 1, points.Count)]; MyVector2 p_after = points[MathUtility.ClampListIndex(i + 1, points.Count)]; MyVector2 pTop = GetIntersectionPoint(p_before, p, p_after, halfWidth, isTopPoint: true); MyVector2 pBottom = GetIntersectionPoint(p_before, p, p_after, halfWidth, isTopPoint: false); topCoordinate.Add(pTop); bottomCoordinate.Add(pBottom); } } //Debug.Log(); for (int i = 0; i < points.Count; i++) { //Skip the first point if it is not connected to the last point if (i == 0 && !isConnected) { continue; } int i_minus_one = MathUtility.ClampListIndex(i - 1, points.Count); MyVector2 p1_T = topCoordinate[i_minus_one]; MyVector2 p1_B = bottomCoordinate[i_minus_one]; MyVector2 p2_T = topCoordinate[i]; MyVector2 p2_B = bottomCoordinate[i]; HashSet <Triangle2> triangles = LineSegment(p1_T, p1_B, p2_T, p2_B); foreach (Triangle2 t in triangles) { lineTriangles.Add(t); } } //Debug.Log(lineTriangles.Count); return(lineTriangles); }
//We assume an edge is visible from a point if the triangle (formed by travering edges in the convex hull //of the existing triangulation) form a clockwise triangle with the point //https://stackoverflow.com/questions/8898255/check-whether-a-point-is-visible-from-a-face-of-a-2d-convex-hull private static HashSet <Triangle2> TriangulatePointsConvexHull(HashSet <MyVector2> pointsHashset) { HashSet <Triangle2> triangles = new HashSet <Triangle2>(); //The points we will add List <MyVector2> originalPoints = new List <MyVector2>(pointsHashset); //Step 1. Sort the points along x-axis //OrderBy is always soring in ascending order - use OrderByDescending to get in the other order //originalPoints = originalPoints.OrderBy(n => n.x).ThenBy(n => n.y).ToList(); originalPoints = originalPoints.OrderBy(n => n.x).ToList(); //Step 2. Create the first triangle so we can start the algorithm //Assumes the convex hull algorithm sorts in the same way in x and y directions as we do above MyVector2 p1Start = originalPoints[0]; MyVector2 p2Start = originalPoints[1]; MyVector2 p3Start = originalPoints[2]; //Theese points for the first triangle Triangle2 newTriangle = new Triangle2(p1Start, p2Start, p3Start); triangles.Add(newTriangle); //All points that form the triangles HashSet <MyVector2> triangulatedPoints = new HashSet <MyVector2>(); triangulatedPoints.Add(p1Start); triangulatedPoints.Add(p2Start); triangulatedPoints.Add(p3Start); //Step 3. Add the other points one-by-one //Add the other points one by one //Starts at 3 because we have already added 0,1,2 for (int i = 3; i < originalPoints.Count; i++) { MyVector2 pointToAdd = originalPoints[i]; //Find the convex hull of the current triangulation //It generates a counter-clockwise convex hull List <MyVector2> pointsOnHull = _ConvexHull.JarvisMarch(triangulatedPoints); if (pointsOnHull == null) { Debug.Log("No points on hull when triangulating"); continue; } bool couldFormTriangle = false; //Loop through all edges in the convex hull for (int j = 0; j < pointsOnHull.Count; j++) { MyVector2 p1 = pointsOnHull[j]; MyVector2 p2 = pointsOnHull[MathUtility.ClampListIndex(j + 1, pointsOnHull.Count)]; //If this triangle is clockwise, then we can see the edge //so we should create a new triangle with this edge and the point if (Geometry.IsTriangleOrientedClockwise(p1, p2, pointToAdd)) { triangles.Add(new Triangle2(p1, p2, pointToAdd)); couldFormTriangle = true; } } //Add the point to the list of all points in the current triangulation //if the point could form a triangle if (couldFormTriangle) { triangulatedPoints.Add(pointToAdd); } } return(triangles); }