/// <summary> /// Reduce the number of sides in this footprint /// </summary> /// <param name="footprint"></param> /// <returns></returns> private static IReadOnlyList <Vector2> Reduce(IReadOnlyList <Vector2> footprint) { Contract.Requires(footprint != null); Contract.Ensures(Contract.Result <IReadOnlyList <Vector2> >() != null); Contract.Ensures(Contract.Result <IReadOnlyList <Vector2> >().Count <= footprint.Count); //Early exit, we can't do anything useful with a line! if (footprint.Count <= 3) { return(footprint); } //Create a list with the points in var p = new Point2DList(); p.AddRange(footprint.Select(a => new Point2D(a.X, a.Y)).ToArray()); //If two consecutive points are in the same position, remove one p.RemoveDuplicateNeighborPoints(); //Merge edges which are parallel (with a tolerance of 1 degree) //p.MergeParallelEdges(0.01745240643); p.Simplify(); //Ensure shape is clockwise wound p.CalculateWindingOrder(); if (p.WindingOrder != Point2DList.WindingOrderType.Clockwise) { if (p.WindingOrder != Point2DList.WindingOrderType.AntiClockwise) { throw new InvalidOperationException("Winding order is neither clockwise or anticlockwise"); } //We're done (but we need to correct the winding) return(p.Select(a => new Vector2(a.Xf, a.Yf)).ToArray()); } //We're done :D return(p.Select(a => new Vector2(a.Xf, a.Yf)).ToArray()); }
/// <summary> /// Performs one or more polygon operations on the 2 provided polygons /// </summary> /// <param name="polygon1">The first polygon.</param> /// <param name="polygon2">The second polygon</param> /// <param name="subtract">The result of the polygon subtraction</param> /// <returns>error code</returns> public static PolygonUtil.PolyUnionError PolygonOperation(PolygonUtil.PolyOperation operations, Point2DList polygon1, Point2DList polygon2, out Dictionary<uint, Point2DList> results) { PolygonOperationContext ctx = new PolygonOperationContext(); ctx.Init(operations, polygon1, polygon2); results = ctx.mOutput; return PolygonUtil.PolygonOperation(ctx); }
/// Merges two polygons, given that they intersect. /// </summary> /// <param name="polygon1">The first polygon.</param> /// <param name="polygon2">The second polygon.</param> /// <param name="union">The union of the two polygons</param> /// <returns>The error returned from union</returns> public static PolygonUtil.PolyUnionError PolygonUnion(Point2DList polygon1, Point2DList polygon2, out Point2DList union) { PolygonOperationContext ctx = new PolygonOperationContext(); ctx.Init(PolygonUtil.PolyOperation.Union, polygon1, polygon2); PolygonUnionInternal(ctx); union = ctx.Union; return ctx.mError; }
/// <summary> /// Subtracts one polygon from another. /// </summary> /// <param name="polygon1">The base polygon.</param> /// <param name="polygon2">The polygon to subtract from the base.</param> /// <param name="subtract">The result of the polygon subtraction</param> /// <returns>error code</returns> public static PolygonUtil.PolyUnionError PolygonSubtract(Point2DList polygon1, Point2DList polygon2, out Point2DList subtract) { PolygonOperationContext ctx = new PolygonOperationContext(); ctx.Init(PolygonUtil.PolyOperation.Subtract, polygon1, polygon2); PolygonSubtractInternal(ctx); subtract = ctx.Subtract; return ctx.mError; }
/// <summary> /// Check and return polygon intersections /// </summary> /// <param name="polygon1"></param> /// <param name="polygon2"></param> /// <param name="intersections"></param> /// <returns></returns> private bool VerticesIntersect(Point2DList polygon1, Point2DList polygon2, out List<EdgeIntersectInfo> intersections) { intersections = new List<EdgeIntersectInfo>(); double epsilon = Math.Min(polygon1.Epsilon, polygon2.Epsilon); // Iterate through polygon1's edges for (int i = 0; i < polygon1.Count; i++) { // Get edge vertices Point2D p1 = polygon1[i]; Point2D p2 = polygon1[polygon1.NextIndex(i)]; // Get intersections between this edge and polygon2 for (int j = 0; j < polygon2.Count; j++) { Point2D point = new Point2D(); Point2D p3 = polygon2[j]; Point2D p4 = polygon2[polygon2.NextIndex(j)]; // Check if the edges intersect if (TriangulationUtil.LinesIntersect2D(p1, p2, p3, p4, ref point, epsilon)) { // Rounding is not needed since we compare using an epsilon. //// Here, we round the returned intersection point to its nearest whole number. //// This prevents floating point anomolies where 99.9999-> is returned instead of 100. //point = new Point2D((float)Math.Round(point.X, 0), (float)Math.Round(point.Y, 0)); // Record the intersection intersections.Add(new EdgeIntersectInfo(new Edge(p1, p2), new Edge(p3, p4), point)); } } } // true if any intersections were found. return (intersections.Count > 0); }
/// <summary> /// * ref: http://ozviz.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/ - Solution 2 /// * Compute the sum of the angles made between the test point and each pair of points making up the polygon. /// * If this sum is 2pi then the point is an interior point, if 0 then the point is an exterior point. /// </summary> public bool PointInPolygonAngle(Point2D point, Point2DList polygon) { double angle = 0; // Iterate through polygon's edges for (int i = 0; i < polygon.Count; i++) { // Get points Point2D p1 = polygon[i] - point; Point2D p2 = polygon[polygon.NextIndex(i)] - point; angle += VectorAngle(p1, p2); } if (Math.Abs(angle) < Math.PI) { return false; } return true; }
public bool Init(PolygonUtil.PolyOperation operations, Point2DList polygon1, Point2DList polygon2) { Clear(); mOperations = operations; mOriginalPolygon1 = polygon1; mOriginalPolygon2 = polygon2; // Make a copy of the polygons so that we dont modify the originals, and // force vertices to integer (pixel) values. mPoly1 = new Point2DList(polygon1); mPoly1.WindingOrder = Point2DList.WindingOrderType.Default; mPoly2 = new Point2DList(polygon2); mPoly2.WindingOrder = Point2DList.WindingOrderType.Default; // Find intersection points if (!VerticesIntersect(mPoly1, mPoly2, out mIntersections)) { // No intersections found - polygons do not overlap. mError = PolygonUtil.PolyUnionError.NoIntersections; return false; } // make sure edges that intersect more than once are updated to have correct start points int numIntersections = mIntersections.Count; for (int i = 0; i < numIntersections; ++i) { for (int j = i + 1; j < numIntersections; ++j) { if (mIntersections[i].EdgeOne.EdgeStart.Equals(mIntersections[j].EdgeOne.EdgeStart) && mIntersections[i].EdgeOne.EdgeEnd.Equals(mIntersections[j].EdgeOne.EdgeEnd)) { mIntersections[j].EdgeOne.EdgeStart = mIntersections[i].IntersectionPoint; } if (mIntersections[i].EdgeTwo.EdgeStart.Equals(mIntersections[j].EdgeTwo.EdgeStart) && mIntersections[i].EdgeTwo.EdgeEnd.Equals(mIntersections[j].EdgeTwo.EdgeEnd)) { mIntersections[j].EdgeTwo.EdgeStart = mIntersections[i].IntersectionPoint; } } } // Add intersection points to original polygons, ignoring existing points. foreach (EdgeIntersectInfo intersect in mIntersections) { if (!mPoly1.Contains(intersect.IntersectionPoint)) { mPoly1.Insert(mPoly1.IndexOf(intersect.EdgeOne.EdgeStart) + 1, intersect.IntersectionPoint); } if (!mPoly2.Contains(intersect.IntersectionPoint)) { mPoly2.Insert(mPoly2.IndexOf(intersect.EdgeTwo.EdgeStart) + 1, intersect.IntersectionPoint); } } mPoly1VectorAngles = new List<int>(); for (int i = 0; i < mPoly2.Count; ++i) { mPoly1VectorAngles.Add(-1); } mPoly2VectorAngles = new List<int>(); for (int i = 0; i < mPoly1.Count; ++i) { mPoly2VectorAngles.Add(-1); } // Find starting point on the edge of polygon1 that is outside of // the intersected area to begin polygon trace. int currentIndex = 0; do { bool bPointInPolygonAngle = PointInPolygonAngle(mPoly1[currentIndex], mPoly2); mPoly2VectorAngles[currentIndex] = bPointInPolygonAngle ? 1 : 0; if (bPointInPolygonAngle) { mStartingIndex = currentIndex; break; } currentIndex = mPoly1.NextIndex(currentIndex); } while (currentIndex != 0); // If we don't find a point on polygon1 thats outside of the // intersect area, the polygon1 must be inside of polygon2, // in which case, polygon2 IS the union of the two. if (mStartingIndex == -1) { mError = PolygonUtil.PolyUnionError.Poly1InsidePoly2; return false; } return true; }
public void Clear() { mOperations = PolygonUtil.PolyOperation.None; mOriginalPolygon1 = null; mOriginalPolygon2 = null; mPoly1 = null; mPoly2 = null; mIntersections = null; mStartingIndex = -1; mError = PolygonUtil.PolyUnionError.None; mPoly1VectorAngles = null; mPoly2VectorAngles = null; mOutput = new Dictionary<uint, Point2DList>(); }
private static List<Point2DList> SplitComplexPolygonCleanup(IList<Point2D> orig) { List<Point2DList> l = new List<Point2DList>(); Point2DList origP2DL = new Point2DList(orig); l.Add(origP2DL); int listIdx = 0; int numLists = l.Count; while (listIdx < numLists) { int numPoints = l[listIdx].Count; for (int i = 0; i < numPoints; ++i) { for (int j = i + 1; j < numPoints; ++j) { if (l[listIdx][i].Equals(l[listIdx][j], origP2DL.Epsilon)) { // found a self-intersection loop - split it off into it's own list int numToRemove = j - i; Point2DList newList = new Point2DList(); for (int k = i + 1; k <= j; ++k) { newList.Add(l[listIdx][k]); } l[listIdx].RemoveRange(i + 1, numToRemove); l.Add(newList); ++numLists; numPoints -= numToRemove; j = i + 1; } } } l[listIdx].Simplify(); ++listIdx; } return l; }
/// <summary> /// Trace the edge of a non-simple polygon and return a simple polygon. /// ///Method: ///Start at vertex with minimum y (pick maximum x one if there are two). ///We aim our "lastDir" vector at (1.0, 0) ///We look at the two rays going off from our start vertex, and follow whichever ///has the smallest angle (in -Pi . Pi) wrt lastDir ("rightest" turn) /// ///Loop until we hit starting vertex: /// ///We add our current vertex to the list. ///We check the seg from current vertex to next vertex for intersections /// - if no intersections, follow to next vertex and continue /// - if intersections, pick one with minimum distance /// - if more than one, pick one with "rightest" next point (two possibilities for each) /// /// </summary> /// <param name="verts"></param> /// <returns></returns> public static List<Point2DList> SplitComplexPolygon(Point2DList verts, double epsilon) { int numVerts = verts.Count; int nNodes = 0; List<SplitComplexPolygonNode> nodes = new List<SplitComplexPolygonNode>(); //Add base nodes (raw outline) for (int i = 0; i < verts.Count; ++i) { SplitComplexPolygonNode newNode = new SplitComplexPolygonNode(new Point2D(verts[i].X, verts[i].Y)); nodes.Add(newNode); } for (int i = 0; i < verts.Count; ++i) { int iplus = (i == numVerts - 1) ? 0 : i + 1; int iminus = (i == 0) ? numVerts - 1 : i - 1; nodes[i].AddConnection(nodes[iplus]); nodes[i].AddConnection(nodes[iminus]); } nNodes = nodes.Count; //Process intersection nodes - horribly inefficient bool dirty = true; int counter = 0; while (dirty) { dirty = false; for (int i = 0; !dirty && i < nNodes; ++i) { for (int j = 0; !dirty && j < nodes[i].NumConnected; ++j) { for (int k = 0; !dirty && k < nNodes; ++k) { if (k == i || nodes[k] == nodes[i][j]) { continue; } for (int l = 0; !dirty && l < nodes[k].NumConnected; ++l) { if (nodes[k][l] == nodes[i][j] || nodes[k][l] == nodes[i]) { continue; } //Check intersection Point2D intersectPt = new Point2D(); //if (counter > 100) printf("checking intersection: %d, %d, %d, %d\n",i,j,k,l); bool crosses = TriangulationUtil.LinesIntersect2D( nodes[i].Position, nodes[i][j].Position, nodes[k].Position, nodes[k][l].Position, true, true, true, ref intersectPt, epsilon); if (crosses) { /*if (counter > 100) { printf("Found crossing at %f, %f\n",intersectPt.x, intersectPt.y); printf("Locations: %f,%f - %f,%f | %f,%f - %f,%f\n", nodes[i].position.x, nodes[i].position.y, nodes[i].connected[j].position.x, nodes[i].connected[j].position.y, nodes[k].position.x,nodes[k].position.y, nodes[k].connected[l].position.x,nodes[k].connected[l].position.y); printf("Memory addresses: %d, %d, %d, %d\n",(int)&nodes[i],(int)nodes[i].connected[j],(int)&nodes[k],(int)nodes[k].connected[l]); }*/ dirty = true; //Destroy and re-hook connections at crossing point SplitComplexPolygonNode intersectionNode = new SplitComplexPolygonNode(intersectPt); int idx = nodes.IndexOf(intersectionNode); if (idx >= 0 && idx < nodes.Count) { intersectionNode = nodes[idx]; } else { nodes.Add(intersectionNode); nNodes = nodes.Count; } SplitComplexPolygonNode nodei = nodes[i]; SplitComplexPolygonNode connij = nodes[i][j]; SplitComplexPolygonNode nodek = nodes[k]; SplitComplexPolygonNode connkl = nodes[k][l]; connij.RemoveConnection(nodei); nodei.RemoveConnection(connij); connkl.RemoveConnection(nodek); nodek.RemoveConnection(connkl); if (!intersectionNode.Position.Equals(nodei.Position, epsilon)) { intersectionNode.AddConnection(nodei); nodei.AddConnection(intersectionNode); } if (!intersectionNode.Position.Equals(nodek.Position, epsilon)) { intersectionNode.AddConnection(nodek); nodek.AddConnection(intersectionNode); } if (!intersectionNode.Position.Equals(connij.Position, epsilon)) { intersectionNode.AddConnection(connij); connij.AddConnection(intersectionNode); } if (!intersectionNode.Position.Equals(connkl.Position, epsilon)) { intersectionNode.AddConnection(connkl); connkl.AddConnection(intersectionNode); } } } } } } ++counter; //if (counter > 100) printf("Counter: %d\n",counter); } // /* // // Debugging: check for connection consistency // for (int i=0; i<nNodes; ++i) { // int nConn = nodes[i].nConnected; // for (int j=0; j<nConn; ++j) { // if (nodes[i].connected[j].nConnected == 0) Assert(false); // SplitComplexPolygonNode* connect = nodes[i].connected[j]; // bool found = false; // for (int k=0; k<connect.nConnected; ++k) { // if (connect.connected[k] == &nodes[i]) found = true; // } // Assert(found); // } // }*/ //Collapse duplicate points bool foundDupe = true; int nActive = nNodes; double epsilonSquared = epsilon * epsilon; while (foundDupe) { foundDupe = false; for (int i = 0; i < nNodes; ++i) { if (nodes[i].NumConnected == 0) { continue; } for (int j = i + 1; j < nNodes; ++j) { if (nodes[j].NumConnected == 0) { continue; } Point2D diff = nodes[i].Position - nodes[j].Position; if (diff.MagnitudeSquared() <= epsilonSquared) { if (nActive <= 3) { throw new Exception("Eliminated so many duplicate points that resulting polygon has < 3 vertices!"); } //printf("Found dupe, %d left\n",nActive); --nActive; foundDupe = true; SplitComplexPolygonNode inode = nodes[i]; SplitComplexPolygonNode jnode = nodes[j]; //Move all of j's connections to i, and remove j int njConn = jnode.NumConnected; for (int k = 0; k < njConn; ++k) { SplitComplexPolygonNode knode = jnode[k]; //Debug.Assert(knode != jnode); if (knode != inode) { inode.AddConnection(knode); knode.AddConnection(inode); } knode.RemoveConnection(jnode); //printf("knode %d on node %d now has %d connections\n",k,j,knode.nConnected); //printf("Found duplicate point.\n"); } jnode.ClearConnections(); // to help with garbage collection nodes.RemoveAt(j); --nNodes; } } } } // /* // // Debugging: check for connection consistency // for (int i=0; i<nNodes; ++i) { // int nConn = nodes[i].nConnected; // printf("Node %d has %d connections\n",i,nConn); // for (int j=0; j<nConn; ++j) { // if (nodes[i].connected[j].nConnected == 0) { // printf("Problem with node %d connection at address %d\n",i,(int)(nodes[i].connected[j])); // Assert(false); // } // SplitComplexPolygonNode* connect = nodes[i].connected[j]; // bool found = false; // for (int k=0; k<connect.nConnected; ++k) { // if (connect.connected[k] == &nodes[i]) found = true; // } // if (!found) printf("Connection %d (of %d) on node %d (of %d) did not have reciprocal connection.\n",j,nConn,i,nNodes); // Assert(found); // } // }//*/ //Now walk the edge of the list //Find node with minimum y value (max x if equal) double minY = double.MaxValue; double maxX = -double.MaxValue; int minYIndex = -1; for (int i = 0; i < nNodes; ++i) { if (nodes[i].Position.Y < minY && nodes[i].NumConnected > 1) { minY = nodes[i].Position.Y; minYIndex = i; maxX = nodes[i].Position.X; } else if (nodes[i].Position.Y == minY && nodes[i].Position.X > maxX && nodes[i].NumConnected > 1) { minYIndex = i; maxX = nodes[i].Position.X; } } Point2D origDir = new Point2D(1.0f, 0.0f); List<Point2D> resultVecs = new List<Point2D>(); SplitComplexPolygonNode currentNode = nodes[minYIndex]; SplitComplexPolygonNode startNode = currentNode; //Debug.Assert(currentNode.nConnected > 0); SplitComplexPolygonNode nextNode = currentNode.GetRightestConnection(origDir); if (nextNode == null) { // Borked, clean up our mess and return return PolygonUtil.SplitComplexPolygonCleanup(verts); } resultVecs.Add(startNode.Position); while (nextNode != startNode) { if (resultVecs.Count > (4 * nNodes)) { //printf("%d, %d, %d\n",(int)startNode,(int)currentNode,(int)nextNode); //printf("%f, %f . %f, %f\n",currentNode.position.x,currentNode.position.y, nextNode.position.x, nextNode.position.y); //verts.printFormatted(); //printf("Dumping connection graph: \n"); //for (int i=0; i<nNodes; ++i) //{ // printf("nodex[%d] = %f; nodey[%d] = %f;\n",i,nodes[i].position.x,i,nodes[i].position.y); // printf("//connected to\n"); // for (int j=0; j<nodes[i].nConnected; ++j) // { // printf("connx[%d][%d] = %f; conny[%d][%d] = %f;\n",i,j,nodes[i].connected[j].position.x, i,j,nodes[i].connected[j].position.y); // } //} //printf("Dumping results thus far: \n"); //for (int i=0; i<nResultVecs; ++i) //{ // printf("x[%d]=map(%f,-3,3,0,width); y[%d] = map(%f,-3,3,height,0);\n",i,resultVecs[i].x,i,resultVecs[i].y); //} //Debug.Assert(false); //nodes should never be visited four times apiece (proof?), so we've probably hit a loop...crap throw new Exception("nodes should never be visited four times apiece (proof?), so we've probably hit a loop...crap"); } resultVecs.Add(nextNode.Position); SplitComplexPolygonNode oldNode = currentNode; currentNode = nextNode; //printf("Old node connections = %d; address %d\n",oldNode.nConnected, (int)oldNode); //printf("Current node connections = %d; address %d\n",currentNode.nConnected, (int)currentNode); nextNode = currentNode.GetRightestConnection(oldNode); if (nextNode == null) { return PolygonUtil.SplitComplexPolygonCleanup(resultVecs); } // There was a problem, so jump out of the loop and use whatever garbage we've generated so far //printf("nextNode address: %d\n",(int)nextNode); } if (resultVecs.Count < 1) { // Borked, clean up our mess and return return PolygonUtil.SplitComplexPolygonCleanup(verts); } else { return PolygonUtil.SplitComplexPolygonCleanup(resultVecs); } }
public static void InitializeHoles(List <Contour> holes, ITriangulatable parent, ConstrainedPointSet cps) { int numHoles = holes.Count; int holeIdx = 0; // pass 1 - remove duplicates while (holeIdx < numHoles) { int hole2Idx = holeIdx + 1; while (hole2Idx < numHoles) { bool bSamePolygon = PolygonUtil.PolygonsAreSame2D(holes[holeIdx], holes[hole2Idx]); if (bSamePolygon) { // remove one of them holes.RemoveAt(hole2Idx); --numHoles; } else { ++hole2Idx; } } ++holeIdx; } // pass 2: Intersections and Containment holeIdx = 0; while (holeIdx < numHoles) { bool bIncrementHoleIdx = true; int hole2Idx = holeIdx + 1; while (hole2Idx < numHoles) { if (PolygonUtil.PolygonContainsPolygon(holes[holeIdx], holes[holeIdx].Bounds, holes[hole2Idx], holes[hole2Idx].Bounds, false)) { holes[holeIdx].AddHole(holes[hole2Idx]); holes.RemoveAt(hole2Idx); --numHoles; } else if (PolygonUtil.PolygonContainsPolygon(holes[hole2Idx], holes[hole2Idx].Bounds, holes[holeIdx], holes[holeIdx].Bounds, false)) { holes[hole2Idx].AddHole(holes[holeIdx]); holes.RemoveAt(holeIdx); --numHoles; bIncrementHoleIdx = false; break; } else { bool bIntersect = PolygonUtil.PolygonsIntersect2D(holes[holeIdx], holes[holeIdx].Bounds, holes[hole2Idx], holes[hole2Idx].Bounds); if (bIntersect) { // this is actually an error condition // fix by merging hole1 and hole2 into hole1 (including the holes inside hole2!) and delete hole2 // Then, because hole1 is now changed, restart it's check. PolygonOperationContext ctx = new PolygonOperationContext(); if (!ctx.Init(PolygonUtil.PolyOperation.Union | PolygonUtil.PolyOperation.Intersect, holes[holeIdx], holes[hole2Idx])) { if (ctx.mError == PolygonUtil.PolyUnionError.Poly1InsidePoly2) { holes[hole2Idx].AddHole(holes[holeIdx]); holes.RemoveAt(holeIdx); --numHoles; bIncrementHoleIdx = false; break; } else { throw new Exception("PolygonOperationContext.Init had an error during initialization"); } } PolygonUtil.PolyUnionError pue = PolygonUtil.PolygonOperation(ctx); if (pue == PolygonUtil.PolyUnionError.None) { Point2DList union = ctx.Union; Point2DList intersection = ctx.Intersect; // create a new contour for the union Contour c = new Contour(parent); c.AddRange(union); c.Name = "(" + holes[holeIdx].Name + " UNION " + holes[hole2Idx].Name + ")"; c.WindingOrder = Point2DList.WindingOrderType.Default; // add children from both of the merged contours int numChildHoles = holes[holeIdx].GetNumHoles(); for (int i = 0; i < numChildHoles; ++i) { c.AddHole(holes[holeIdx].GetHole(i)); } numChildHoles = holes[hole2Idx].GetNumHoles(); for (int i = 0; i < numChildHoles; ++i) { c.AddHole(holes[hole2Idx].GetHole(i)); } // make sure we preserve the contours of the intersection Contour cInt = new Contour(c); cInt.AddRange(intersection); cInt.Name = "(" + holes[holeIdx].Name + " INTERSECT " + holes[hole2Idx].Name + ")"; cInt.WindingOrder = Point2DList.WindingOrderType.Default; c.AddHole(cInt); // replace the current contour with the merged contour holes[holeIdx] = c; // toss the second contour holes.RemoveAt(hole2Idx); --numHoles; // current hole is "examined", so move to the next one hole2Idx = holeIdx + 1; } else { throw new Exception("PolygonOperation had an error!"); } } else { ++hole2Idx; } } } if (bIncrementHoleIdx) { ++holeIdx; } } numHoles = holes.Count; holeIdx = 0; while (holeIdx < numHoles) { int numPoints = holes[holeIdx].Count; for (int i = 0; i < numPoints; ++i) { int j = holes[holeIdx].NextIndex(i); uint constraintCode = TriangulationConstraint.CalculateContraintCode(holes[holeIdx][i], holes[holeIdx][j]); TriangulationConstraint tc = null; if (!cps.TryGetConstraint(constraintCode, out tc)) { tc = new TriangulationConstraint(holes[holeIdx][i], holes[holeIdx][j]); cps.AddConstraint(tc); } // replace the points in the holes with valid points if (holes[holeIdx][i].VertexCode == tc.P.VertexCode) { holes[holeIdx][i] = tc.P; } else if (holes[holeIdx][j].VertexCode == tc.P.VertexCode) { holes[holeIdx][j] = tc.P; } if (holes[holeIdx][i].VertexCode == tc.Q.VertexCode) { holes[holeIdx][i] = tc.Q; } else if (holes[holeIdx][j].VertexCode == tc.Q.VertexCode) { holes[holeIdx][j] = tc.Q; } } ++holeIdx; } }