//Help method to calculate the intersection point between two planes offset in normal direction by a width private static MyVector2 GetIntersectionPoint(MyVector2 a, MyVector2 b, MyVector2 c, float halfWidth, bool isTopPoint) { //Direction of the lines going to and from point b MyVector2 beforeDir = MyVector2.Normalize(b - a); MyVector2 afterDir = MyVector2.Normalize(c - b); MyVector2 beforeNormal = GetNormal(a, b); MyVector2 afterNormal = GetNormal(b, c); //Compare the normals! //normalDirFactor is used to determine if we want to top point (same direction as normal) float normalDirFactor = isTopPoint ? 1f : -1f; //If they are the same it means we have a straight line and thus we cant do plane-plane intersection //if (beforeNormal.Equals(afterNormal)) //When comparing the normals, we cant use the regular small value because then //the line width goes to infinity when doing plane-plane intersection float dot = MyVector2.Dot(beforeNormal, afterNormal); //Dot is 1 if the point in the same dir and -1 if the point in the opposite dir float one = 1f - 0.01f; if (dot > one || dot < -one) { MyVector2 averageNormal = MyVector2.Normalize((afterNormal + beforeNormal) * 0.5f); MyVector2 intersectionPoint = b + averageNormal * halfWidth * normalDirFactor; return(intersectionPoint); } else { //Now we can calculate where the plane starts MyVector2 beforePlanePos = b + beforeNormal * halfWidth * normalDirFactor; MyVector2 afterPlanePos = b + afterNormal * halfWidth * normalDirFactor; //Calculate the intersection point //We know they are intersecting, so we don't need to test that MyVector2 intersectionPoint = Intersections.GetPlanePlaneIntersectionPoint(beforePlanePos, beforeNormal, afterPlanePos, afterNormal); return(intersectionPoint); } }
//Is a polygon One inside polygon Two? private static bool IsPolygonInsidePolygon(List <MyVector2> polyOne, List <MyVector2> polyTwo) { bool isInside = false; for (int i = 0; i < polyOne.Count; i++) { if (Intersections.PointPolygon(polyTwo, polyOne[i])) { //Is inside if at least one point is inside the polygon. We run this method after we have tested //if the polygons are intersecting isInside = true; break; } } return(isInside); }
//Mark entry exit points private static void MarkEntryExit(List <ClipVertex> poly, List <MyVector2> clipPolyVector) { //First see if the first vertex starts inside or outside (we can use the original list) bool isInside = Intersections.PointPolygon(clipPolyVector, poly[0].coordinate); //Debug.Log(isInside); ClipVertex currentVertex = poly[0]; ClipVertex firstVertex = currentVertex; int safety = 0; while (true) { if (currentVertex.isIntersection) { //If we were outside, this is an entry currentVertex.isEntry = isInside ? false : true; //Now we know we are either inside or outside isInside = !isInside; } currentVertex = currentVertex.next; //We have travelled around the entire polygon if (currentVertex.Equals(firstVertex)) { break; } safety += 1; if (safety > 100000) { Debug.Log("Endless loop in mark entry exit"); break; } } }
//Is an edge crossing another edge? private static bool IsEdgeCrossingEdge(MyVector2 e1_p1, MyVector2 e1_p2, MyVector2 e2_p1, MyVector2 e2_p2) { //We will here run into floating point precision issues so we have to be careful //To solve that you can first check the end points //and modify the line-line intersection algorithm to include a small epsilon //First check if the edges are sharing a point, if so they are not crossing if (e1_p1.Equals(e2_p1) || e1_p1.Equals(e2_p2) || e1_p2.Equals(e2_p1) || e1_p2.Equals(e2_p2)) { return(false); } //Then check if the lines are intersecting if (!Intersections.LineLine(e1_p1, e1_p2, e2_p1, e2_p2, shouldIncludeEndPoints: false)) { return(false); } return(true); }
// // 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); }
// // 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); }
//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.DistanceFromPointToPlane(plane.normal, plane.pos, v1); float dist_to_v2 = Geometry.DistanceFromPointToPlane(plane.normal, plane.pos, v2); //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); MyVector2 intersectionPoint = Intersections.GetRayPlaneIntersectionPoint(plane.pos, plane.normal, v1, rayDir); 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); MyVector2 intersectionPoint = Intersections.GetRayPlaneIntersectionPoint(plane.pos, plane.normal, v1, rayDir); 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); }
public static List <List <MyVector2> > ClipPolygons(List <MyVector2> polyVector2, List <MyVector2> clipPolyVector2, BooleanOperation booleanOperation) { List <List <MyVector2> > finalPoly = new List <List <MyVector2> >(); //Step 0. Create the data structure needed List <ClipVertex> poly = InitDataStructure(polyVector2); List <ClipVertex> clipPoly = InitDataStructure(clipPolyVector2); //Step 1. Find intersection points //Need to test if we have found an intersection point, //if none is found, the polygons dont intersect, or one polygon is inside the other bool hasFoundIntersection = false; for (int i = 0; i < poly.Count; i++) { ClipVertex currentVertex = poly[i]; //Important to use iPlusOne because poly.next may change int iPlusOne = MathUtility.ClampListIndex(i + 1, poly.Count); MyVector2 a = poly[i].coordinate; MyVector2 b = poly[iPlusOne].coordinate; //Gizmos.DrawWireSphere(poly[i].coordinate, 0.02f); //Gizmos.DrawWireSphere(poly[i].next.coordinate, 0.02f); for (int j = 0; j < clipPoly.Count; j++) { int jPlusOne = MathUtility.ClampListIndex(j + 1, clipPoly.Count); MyVector2 c = clipPoly[j].coordinate; MyVector2 d = clipPoly[jPlusOne].coordinate; //Are these lines intersecting? if (Intersections.LineLine(a, b, c, d, true)) { hasFoundIntersection = true; MyVector2 intersectionPoint2D = Intersections.GetLineLineIntersectionPoint(a, b, c, d); //Vector3 intersectionPoint = new Vector3(intersectionPoint2D.x, 0f, intersectionPoint2D.y); //Gizmos.color = Color.red; //Gizmos.DrawWireSphere(intersectionPoint, 0.04f); //We need to insert this intersection vertex into both polygons //Insert into the polygon ClipVertex vertexOnPolygon = InsertIntersectionVertex(a, b, intersectionPoint2D, currentVertex); //Insert into the clip polygon ClipVertex vertexOnClipPolygon = InsertIntersectionVertex(c, d, intersectionPoint2D, clipPoly[j]); //Also connect the intersection vertices with each other vertexOnPolygon.neighbor = vertexOnClipPolygon; vertexOnClipPolygon.neighbor = vertexOnPolygon; } } } //Debug in which order the vertices are in the linked list //InWhichOrderAreVerticesAdded(poly); //InWhichOrderAreVerticesAdded(clipPoly); //If the polygons are intersecting if (hasFoundIntersection) { //Step 2. Trace each polygon and mark entry and exit points to the other polygon's interior MarkEntryExit(poly, clipPolyVector2); MarkEntryExit(clipPoly, polyVector2); //Debug entry exit points DebugEntryExit(poly); //DebugEntryExit(clipPoly); //Step 3. Create the desired clipped polygon if (booleanOperation == BooleanOperation.Intersection) { //Where the two polygons intersect List <ClipVertex> intersectionVertices = GetClippedPolygon(poly, true); //Debug.Log(intersectionVertices.Count); AddPolygonToList(intersectionVertices, finalPoly, false); //Debug.Log(); } else if (booleanOperation == BooleanOperation.Difference) { //Whats outside of the polygon that doesnt intersect List <ClipVertex> outsidePolyVertices = GetClippedPolygon(poly, false); AddPolygonToList(outsidePolyVertices, finalPoly, true); } else if (booleanOperation == BooleanOperation.ExclusiveOr) { //Whats outside of the polygon that doesnt intersect List <ClipVertex> outsidePolyVertices = GetClippedPolygon(poly, false); AddPolygonToList(outsidePolyVertices, finalPoly, true); //Whats outside of the polygon that doesnt intersect List <ClipVertex> outsideClipPolyVertices = GetClippedPolygon(clipPoly, false); AddPolygonToList(outsideClipPolyVertices, finalPoly, true); } else if (booleanOperation == BooleanOperation.Union) { //Where the two polygons intersect List <ClipVertex> intersectionVertices = GetClippedPolygon(poly, true); AddPolygonToList(intersectionVertices, finalPoly, false); //Whats outside of the polygon that doesnt intersect List <ClipVertex> outsidePolyVertices = GetClippedPolygon(poly, false); AddPolygonToList(outsidePolyVertices, finalPoly, true); //Whats outside of the polygon that doesnt intersect List <ClipVertex> outsideClipPolyVertices = GetClippedPolygon(clipPoly, false); AddPolygonToList(outsideClipPolyVertices, finalPoly, true); } //Where the two polygons intersect //List<ClipVertex> intersectionVertices = GetClippedPolygon(poly, true); //Whats outside of the polygon that doesnt intersect //These will be in clockwise order so remember to change to counter clockwise //List<ClipVertex> outsidePolyVertices = GetClippedPolygon(poly, false); //List<ClipVertex> outsideClipPolyVertices = GetClippedPolygon(clipPoly, false); } //Check if one polygon is inside the other else { //Is the polygon inside the clip polygon? //Depending on the type of boolean operation, we might get a hole if (IsPolygonInsidePolygon(polyVector2, clipPolyVector2)) { Debug.Log("Poly is inside clip poly"); } else if (IsPolygonInsidePolygon(clipPolyVector2, polyVector2)) { Debug.Log("Clip poly is inside poly"); } else { Debug.Log("Polygons are not intersecting"); } } return(finalPoly); }
//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 <MyVector2> ClipPolygon(List <MyVector2> poly, List <Plane2> clippingPlanes) { //Clone the vertices because we will remove vertices from this list List <MyVector2> vertices = new List <MyVector2>(poly); //Save the new vertices temporarily in this list before transfering them to vertices List <MyVector2> vertices_tmp = new List <MyVector2>(); //Clip the polygon for (int i = 0; i < clippingPlanes.Count; i++) { Plane2 plane = clippingPlanes[i]; 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.DistanceFromPointToPlane(plane.normal, plane.pos, v1); float dist_to_v2 = Geometry.DistanceFromPointToPlane(plane.normal, plane.pos, v2); //TODO: What will happen if they are exactly 0? Should maybe use a tolerance of 0.001 //Case 1. Both are outside (= to the right), do nothing //Case 2. Both are inside (= to the left), save v2 if (dist_to_v1 >= 0f && dist_to_v2 >= 0f) { vertices_tmp.Add(v2); } //Case 3. Outside -> Inside, save intersection point and v2 else if (dist_to_v1 < 0f && dist_to_v2 >= 0f) { MyVector2 rayDir = MyVector2.Normalize(v2 - v1); MyVector2 intersectionPoint = Intersections.GetRayPlaneIntersectionPoint(plane.pos, plane.normal, v1, rayDir); vertices_tmp.Add(intersectionPoint); vertices_tmp.Add(v2); } //Case 4. Inside -> Outside, save intersection point else if (dist_to_v1 >= 0f && dist_to_v2 < 0f) { MyVector2 rayDir = MyVector2.Normalize(v2 - v1); MyVector2 intersectionPoint = Intersections.GetRayPlaneIntersectionPoint(plane.pos, plane.normal, v1, rayDir); vertices_tmp.Add(intersectionPoint); } } //Add the new vertices to the list of vertices vertices.Clear(); vertices.AddRange(vertices_tmp); vertices_tmp.Clear(); } return(vertices); }