//------------------------------------------------------------------------------ public bool AddPolygon(PolygonClp pg, PolyType polyType) { int len = pg.Count; if (len < 3) return false; Int64 maxVal; if (m_UseFullRange) maxVal = hiRange; else maxVal = loRange; RangeTest(pg[0], ref maxVal); PolygonClp p = new PolygonClp(len); p.Add(new IntPoint(pg[0].X, pg[0].Y)); int j = 0; for (int i = 1; i < len; ++i) { RangeTest(pg[i], ref maxVal); if (PointsEqual(p[j], pg[i])) continue; else if (j > 0 && SlopesEqual(p[j - 1], p[j], pg[i], maxVal == hiRange)) { if (PointsEqual(p[j - 1], pg[i])) j--; } else j++; if (j < p.Count) p[j] = pg[i]; else p.Add(new IntPoint(pg[i].X, pg[i].Y)); } if (j < 2) return false; m_UseFullRange = maxVal == hiRange; len = j + 1; while (len > 2) { //nb: test for point equality before testing slopes ... if (PointsEqual(p[j], p[0])) j--; else if (PointsEqual(p[0], p[1]) || SlopesEqual(p[j], p[0], p[1], m_UseFullRange)) p[0] = p[j--]; else if (SlopesEqual(p[j - 1], p[j], p[0], m_UseFullRange)) j--; else if (SlopesEqual(p[0], p[1], p[2], m_UseFullRange)) { for (int i = 2; i <= j; ++i) p[i - 1] = p[i]; j--; } else break; len--; } if (len < 3) return false; //create a new edge array ... List<TEdge> edges = new List<TEdge>(len); for (int i = 0; i < len; i++) edges.Add(new TEdge()); m_edges.Add(edges); //convert vertices to a double-linked-list of edges and initialize ... edges[0].xcurr = p[0].X; edges[0].ycurr = p[0].Y; InitEdge(edges[len - 1], edges[0], edges[len - 2], p[len - 1], polyType); for (int i = len - 2; i > 0; --i) InitEdge(edges[i], edges[i + 1], edges[i - 1], p[i], polyType); InitEdge(edges[0], edges[1], edges[len - 1], p[0], polyType); //reset xcurr & ycurr and find 'eHighest' (given the Y axis coordinates //increase downward so the 'highest' edge will have the smallest ytop) ... TEdge e = edges[0]; TEdge eHighest = e; do { e.xcurr = e.xbot; e.ycurr = e.ybot; if (e.ytop < eHighest.ytop) eHighest = e; e = e.next; } while (e != edges[0]); //make sure eHighest is positioned so the following loop works safely ... if (eHighest.windDelta > 0) eHighest = eHighest.next; if (eHighest.dx == horizontal) eHighest = eHighest.next; //finally insert each local minima ... e = eHighest; do { e = AddBoundsToLML(e); } while (e != eHighest); return true; }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------ // SimplifyPolygon functions ... // Convert self-intersecting polygons into simple polygons //------------------------------------------------------------------------------ public static PolygonsClp SimplifyPolygon(PolygonClp poly, PolyFillType fillType = PolyFillType.pftEvenOdd) { PolygonsClp result = new PolygonsClp(); Clipper c = new Clipper(); c.ForceSimple = true; c.AddPolygon(poly, PolyType.ptSubject); c.Execute(ClipType.ctUnion, result, fillType, fillType); return result; }
//------------------------------------------------------------------------------ public static PolygonClp CleanPolygon(PolygonClp poly, double distance = 1.415) { //distance = proximity in units/pixels below which vertices //will be stripped. Default ~= sqrt(2) so when adjacent //vertices have both x & y coords within 1 unit, then //the second vertex will be stripped. double distSqrd = (distance * distance); int highI = poly.Count - 1; PolygonClp result = new PolygonClp(highI + 1); while (highI > 0 && PointsAreClose(poly[highI], poly[0], distSqrd)) highI--; if (highI < 2) return result; IntPoint pt = poly[highI]; int i = 0; for (; ; ) { while (i < highI && PointsAreClose(pt, poly[i], distSqrd)) i += 2; int i2 = i; while (i < highI && (PointsAreClose(poly[i], poly[i + 1], distSqrd) || SlopesNearColinear(pt, poly[i], poly[i + 1], distSqrd))) i++; if (i >= highI) break; else if (i != i2) continue; pt = poly[i++]; result.Add(pt); } if (i <= highI) result.Add(poly[i]); i = result.Count; if (i > 2 && SlopesNearColinear(result[i - 2], result[i - 1], result[0], distSqrd)) result.RemoveAt(i - 1); if (result.Count < 3) result.Clear(); return result; }
//------------------------------------------------------------------------------ public PolyOffsetBuilder(PolygonsClp pts, PolygonsClp solution, bool isPolygon, double delta, JoinType jointype, EndType endtype, double limit = 0) { //precondition: solution != pts if (delta == 0) { solution = pts; return; } m_p = pts; m_delta = delta; m_rmin = 0.5; if (jointype == JoinType.jtMiter) { if (limit > 2) m_rmin = 2.0 / (limit * limit); limit = 0.25; //just in case endtype == etRound } else { if (limit <= 0) limit = 0.25; else if (limit > Math.Abs(delta)) limit = Math.Abs(delta); } double deltaSq = delta * delta; solution.Clear(); solution.Capacity = pts.Count; for (m_i = 0; m_i < pts.Count; m_i++) { int len = pts[m_i].Count; if (len == 0 || (len < 3 && delta <= 0)) continue; else if (len == 1) { currentPoly = new PolygonClp(); currentPoly = BuildArc(pts[m_i][0], 0, 2 * Math.PI, delta, limit); solution.Add(currentPoly); continue; } bool forceClose = PointsEqual(pts[m_i][0], pts[m_i][len - 1]); if (forceClose) len--; //build normals ... normals.Clear(); normals.Capacity = len; for (int j = 0; j < len - 1; ++j) normals.Add(GetUnitNormal(pts[m_i][j], pts[m_i][j + 1])); if (isPolygon || forceClose) normals.Add(GetUnitNormal(pts[m_i][len - 1], pts[m_i][0])); else normals.Add(new DoublePoint(normals[len - 2])); currentPoly = new PolygonClp(); if (isPolygon || forceClose) { m_k = len - 1; for (m_j = 0; m_j < len; ++m_j) OffsetPoint(jointype, limit); solution.Add(currentPoly); if (!isPolygon) { currentPoly = new PolygonClp(); m_delta = -m_delta; m_k = len - 1; for (m_j = 0; m_j < len; ++m_j) OffsetPoint(jointype, limit); m_delta = -m_delta; currentPoly.Reverse(); solution.Add(currentPoly); } } else { m_k = 0; for (m_j = 1; m_j < len - 1; ++m_j) OffsetPoint(jointype, limit); IntPoint pt1; if (endtype == EndType.etButt) { m_j = len - 1; pt1 = new IntPoint((Int64)Round(pts[m_i][m_j].X + normals[m_j].X * delta), (Int64)Round(pts[m_i][m_j].Y + normals[m_j].Y * delta)); AddPoint(pt1); pt1 = new IntPoint((Int64)Round(pts[m_i][m_j].X - normals[m_j].X * delta), (Int64)Round(pts[m_i][m_j].Y - normals[m_j].Y * delta)); AddPoint(pt1); } else { m_j = len - 1; m_k = len - 2; normals[m_j].X = -normals[m_j].X; normals[m_j].Y = -normals[m_j].Y; if (endtype == EndType.etSquare) DoSquare(); else DoRound(limit); } //re-build Normals ... for (int j = len - 1; j > 0; j--) { normals[j].X = -normals[j - 1].X; normals[j].Y = -normals[j - 1].Y; } normals[0].X = -normals[1].X; normals[0].Y = -normals[1].Y; m_k = len - 1; for (m_j = m_k - 1; m_j > 0; --m_j) OffsetPoint(jointype, limit); if (endtype == EndType.etButt) { pt1 = new IntPoint((Int64)Round(pts[m_i][0].X - normals[0].X * delta), (Int64)Round(pts[m_i][0].Y - normals[0].Y * delta)); AddPoint(pt1); pt1 = new IntPoint((Int64)Round(pts[m_i][0].X + normals[0].X * delta), (Int64)Round(pts[m_i][0].Y + normals[0].Y * delta)); AddPoint(pt1); } else { m_k = 1; if (endtype == EndType.etSquare) DoSquare(); else DoRound(limit); } solution.Add(currentPoly); } } //finally, clean up untidy corners ... Clipper clpr = new Clipper(); clpr.AddPolygons(solution, PolyType.ptSubject); if (delta > 0) { clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftPositive, PolyFillType.pftPositive); } else { IntRect r = clpr.GetBounds(); PolygonClp outer = new PolygonClp(4); outer.Add(new IntPoint(r.left - 10, r.bottom + 10)); outer.Add(new IntPoint(r.right + 10, r.bottom + 10)); outer.Add(new IntPoint(r.right + 10, r.top - 10)); outer.Add(new IntPoint(r.left - 10, r.top - 10)); clpr.AddPolygon(outer, PolyType.ptSubject); clpr.ReverseSolution = true; clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftNegative, PolyFillType.pftNegative); if (solution.Count > 0) solution.RemoveAt(0); } }
//------------------------------------------------------------------------------ public static PolygonsClp OffsetPolyLines(PolygonsClp lines, double delta, JoinType jointype, EndType endtype, double limit) { PolygonsClp result = new PolygonsClp(); //automatically strip duplicate points because it gets complicated with //open and closed lines and when to strip duplicates across begin-end ... PolygonsClp pts = new PolygonsClp(lines); for (int i = 0; i < pts.Count; ++i) { for (int j = pts[i].Count - 1; j > 0; j--) if (PointsEqual(pts[i][j], pts[i][j - 1])) pts[i].RemoveAt(j); } if (endtype == EndType.etClosed) { int sz = pts.Count; pts.Capacity = sz * 2; for (int i = 0; i < sz; ++i) { PolygonClp line = new PolygonClp(pts[i]); line.Reverse(); pts.Add(line); } new PolyOffsetBuilder(pts, result, true, delta, jointype, endtype, limit); } else new PolyOffsetBuilder(pts, result, false, delta, jointype, endtype, limit); return result; }
//------------------------------------------------------------------------------ // OffsetPolygon functions ... //------------------------------------------------------------------------------ internal static PolygonClp BuildArc(IntPoint pt, double a1, double a2, double r, double limit) { //see notes in clipper.pas regarding steps double arcFrac = Math.Abs(a2 - a1) / (2 * Math.PI); int steps = (int)(arcFrac * Math.PI / Math.Acos(1 - limit / Math.Abs(r))); if (steps < 2) steps = 2; else if (steps > (int)(222.0 * arcFrac)) steps = (int)(222.0 * arcFrac); double x = Math.Cos(a1); double y = Math.Sin(a1); double c = Math.Cos((a2 - a1) / steps); double s = Math.Sin((a2 - a1) / steps); PolygonClp result = new PolygonClp(steps + 1); for (int i = 0; i <= steps; ++i) { result.Add(new IntPoint(pt.X + Round(x * r), pt.Y + Round(y * r))); double x2 = x; x = x * c - s * y; //cross product y = x2 * s + y * c; //dot product } return result; }
//------------------------------------------------------------------------------ public static double Area(PolygonClp poly) { int highI = poly.Count - 1; if (highI < 2) return 0; if (FullRangeNeeded(poly)) { Int128 a = new Int128(0); a = Int128.Int128Mul(poly[highI].X + poly[0].X, poly[0].Y - poly[highI].Y); for (int i = 1; i <= highI; ++i) a += Int128.Int128Mul(poly[i - 1].X + poly[i].X, poly[i].Y - poly[i - 1].Y); return a.ToDouble() / 2; } else { double area = ((double)poly[highI].X + poly[0].X) * ((double)poly[0].Y - poly[highI].Y); for (int i = 1; i <= highI; ++i) area += ((double)poly[i - 1].X + poly[i].X) * ((double)poly[i].Y - poly[i - 1].Y); return area / 2; } }
//------------------------------------------------------------------------------ private static bool FullRangeNeeded(PolygonClp pts) { bool result = false; for (int i = 0; i < pts.Count; i++) { if (Math.Abs(pts[i].X) > hiRange || Math.Abs(pts[i].Y) > hiRange) throw new ClipperException("Coordinate exceeds range bounds."); else if (Math.Abs(pts[i].X) > loRange || Math.Abs(pts[i].Y) > loRange) result = true; } return result; }
//------------------------------------------------------------------------------ private void BuildResult(PolygonsClp polyg) { polyg.Clear(); polyg.Capacity = m_PolyOuts.Count; for (int i = 0; i < m_PolyOuts.Count; i++) { OutRec outRec = m_PolyOuts[i]; if (outRec.pts == null) continue; OutPt p = outRec.pts; int cnt = PointCount(p); if (cnt < 3) continue; PolygonClp pg = new PolygonClp(cnt); for (int j = 0; j < cnt; j++) { pg.Add(p.pt); p = p.prev; } polyg.Add(pg); } }
//------------------------------------------------------------------------------ public static bool Orientation(PolygonClp poly) { return Area(poly) >= 0; }