public PolyOffsetBuilder(Polygons pts, Polygons solution, double delta, JoinType jointype, double MiterLimit = 2) { //precondtion: solution != pts if (delta == 0) { solution = pts; return; } this.pts = pts; this.delta = delta; if (MiterLimit <= 1) MiterLimit = 1; double RMin = 2/(MiterLimit*MiterLimit); normals = new List<DoublePoint>(); 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 > 1 && pts[m_i][0].X == pts[m_i][len - 1].X && pts[m_i][0].Y == pts[m_i][len - 1].Y) len--; if (len == 0 || (len < 3 && delta <= 0)) continue; else if (len == 1) { Polygon arc; arc = BuildArc(pts[m_i][len - 1], 0, 2 * Math.PI, delta); solution.Add(arc); continue; } //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])); normals.Add(GetUnitNormal(pts[m_i][len - 1], pts[m_i][0])); currentPoly = new Polygon(); m_k = len - 1; for (m_j = 0; m_j < len; ++m_j) { switch (jointype) { case JoinType.jtMiter: { m_R = 1 + (normals[m_j].X*normals[m_k].X + normals[m_j].Y*normals[m_k].Y); if (m_R >= RMin) DoMiter(); else DoSquare(MiterLimit); break; } case JoinType.jtRound: DoRound(); break; case JoinType.jtSquare: DoSquare(1); break; } m_k = m_j; } 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(); Polygon outer = new Polygon(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.Execute(ClipType.ctUnion, solution, PolyFillType.pftNegative, PolyFillType.pftNegative); if (solution.Count > 0) { solution.RemoveAt(0); for (int i = 0; i < solution.Count; i++) solution[i].Reverse(); } } }
//------------------------------------------------------------------------------ public PolyOffsetBuilder(Polygons pts, Polygons 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 Polygon(); 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 Polygon(); 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 Polygon(); 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(); Polygon outer = new Polygon(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 PolyOffsetBuilder(Polygons pts, Polygons solution, double delta, JoinType jointype, double MiterLimit = 2, bool AutoFix = true) { //precondtion: solution != pts if (delta == 0) { solution = pts; return; } this.pts = pts; this.delta = delta; //AutoFix - fixes polygon orientation if necessary and removes //duplicate vertices. Can be set false when you're sure that polygon //orientation is correct and that there are no duplicate vertices. if (AutoFix) { int Len = pts.Count, botI = 0; while (botI < Len && pts[botI].Count == 0) botI++; if (botI == Len) return; //botPt: used to find the lowermost (in inverted Y-axis) & leftmost point //This point (on pts[botI]) must be on an outer polygon ring and if //its orientation is false (counterclockwise) then assume all polygons //need reversing ... IntPoint botPt = pts[botI][0]; for (int i = botI; i < Len; ++i) { if (pts[i].Count == 0) continue; if (UpdateBotPt(pts[i][0], ref botPt)) botI = i; for (int j = pts[i].Count -1; j > 0; j--) { if (PointsEqual(pts[i][j], pts[i][j -1])) pts[i].RemoveAt(j); else if (UpdateBotPt(pts[i][j], ref botPt)) botI = i; } } if (!Orientation(pts[botI])) ReversePolygons(pts); } if (MiterLimit <= 1) MiterLimit = 1; double RMin = 2.0 / (MiterLimit*MiterLimit); normals = new List<DoublePoint>(); 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 > 1 && pts[m_i][0].X == pts[m_i][len - 1].X && pts[m_i][0].Y == pts[m_i][len - 1].Y) len--; if (len == 0 || (len < 3 && delta <= 0)) continue; else if (len == 1) { Polygon arc; arc = BuildArc(pts[m_i][len - 1], 0, 2 * Math.PI, delta); solution.Add(arc); continue; } //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])); normals.Add(GetUnitNormal(pts[m_i][len - 1], pts[m_i][0])); currentPoly = new Polygon(); m_k = len - 1; for (m_j = 0; m_j < len; ++m_j) { switch (jointype) { case JoinType.jtMiter: { m_R = 1 + (normals[m_j].X*normals[m_k].X + normals[m_j].Y*normals[m_k].Y); if (m_R >= RMin) DoMiter(); else DoSquare(MiterLimit); break; } case JoinType.jtRound: DoRound(); break; case JoinType.jtSquare: DoSquare(1); break; } m_k = m_j; } 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(); Polygon outer = new Polygon(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.Execute(ClipType.ctUnion, solution, PolyFillType.pftNegative, PolyFillType.pftNegative); if (solution.Count > 0) { solution.RemoveAt(0); for (int i = 0; i < solution.Count; i++) solution[i].Reverse(); } } }
//------------------------------------------------------------------------------ public PolyOffsetBuilder(Paths pts, out Paths solution, double delta, JoinType jointype, EndType endtype, double limit = 0) { //precondition: solution != pts solution = new Paths(); if (ClipperBase.near_zero(delta)) {solution = pts; return; } m_p = pts; if (endtype != EndType.etClosed && delta < 0) delta = -delta; m_delta = delta; if (jointype == JoinType.jtMiter) { //m_miterVal: see offset_triginometry.svg in the documentation folder ... if (limit > 2) m_miterLim = 2 / (limit * limit); else m_miterLim = 0.5; if (endtype == EndType.etRound) limit = 0.25; } if (jointype == JoinType.jtRound || endtype == EndType.etRound) { if (limit <= 0) limit = 0.25; else if (limit > Math.Abs(delta)*0.25) limit = Math.Abs(delta)*0.25; //m_roundVal: see offset_triginometry2.svg in the documentation folder ... m_Steps360 = Math.PI / Math.Acos(1 - limit / Math.Abs(delta)); m_sin = Math.Sin(2 * Math.PI / m_Steps360); m_cos = Math.Cos(2 * Math.PI / m_Steps360); m_Steps360 /= Math.PI * 2; if (delta < 0) m_sin = -m_sin; } double deltaSq = delta * delta; 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; if (len == 1) { if (jointype == JoinType.jtRound) { double X = 1.0, Y = 0.0; for (cInt j = 1; j <= Round(m_Steps360 * 2 * Math.PI); j++) { AddPoint(new IntPoint( Round(m_p[m_i][0].X + X * delta), Round(m_p[m_i][0].Y + Y * delta))); double X2 = X; X = X * m_cos - m_sin * Y; Y = X2 * m_sin + Y * m_cos; } } else { double X = -1.0, Y = -1.0; for (int j = 0; j < 4; ++j) { AddPoint(new IntPoint(Round(m_p[m_i][0].X + X * delta), Round(m_p[m_i][0].Y + Y * delta))); if (X < 0) X = 1; else if (Y < 0) Y = 1; else X = -1; } } continue; } //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 (endtype == EndType.etClosed) normals.Add(GetUnitNormal(pts[m_i][len - 1], pts[m_i][0])); else normals.Add(new DoublePoint(normals[len - 2])); currentPoly = new Path(); if (endtype == EndType.etClosed) { m_k = len - 1; for (m_j = 0; m_j < len; ++m_j) OffsetPoint(jointype); solution.Add(currentPoly); } else { m_k = 0; for (m_j = 1; m_j < len - 1; ++m_j) OffsetPoint(jointype); IntPoint pt1; if (endtype == EndType.etButt) { m_j = len - 1; pt1 = new IntPoint((cInt)Round(pts[m_i][m_j].X + normals[m_j].X * delta), (cInt)Round(pts[m_i][m_j].Y + normals[m_j].Y * delta)); AddPoint(pt1); pt1 = new IntPoint((cInt)Round(pts[m_i][m_j].X - normals[m_j].X * delta), (cInt)Round(pts[m_i][m_j].Y - normals[m_j].Y * delta)); AddPoint(pt1); } else { m_j = len - 1; m_k = len - 2; m_sinA = 0; normals[m_j] = new DoublePoint(-normals[m_j].X, -normals[m_j].Y); if (endtype == EndType.etSquare) DoSquare(); else DoRound(); } //re-build Normals ... for (int j = len - 1; j > 0; j--) normals[j] = new DoublePoint(-normals[j - 1].X, -normals[j - 1].Y); normals[0] = new DoublePoint(-normals[1].X, -normals[1].Y); m_k = len - 1; for (m_j = m_k - 1; m_j > 0; --m_j) OffsetPoint(jointype); if (endtype == EndType.etButt) { pt1 = new IntPoint((cInt)Round(pts[m_i][0].X - normals[0].X * delta), (cInt)Round(pts[m_i][0].Y - normals[0].Y * delta)); AddPoint(pt1); pt1 = new IntPoint((cInt)Round(pts[m_i][0].X + normals[0].X * delta), (cInt)Round(pts[m_i][0].Y + normals[0].Y * delta)); AddPoint(pt1); } else { m_k = 1; m_sinA = 0; if (endtype == EndType.etSquare) DoSquare(); else DoRound(); } solution.Add(currentPoly); } } //finally, clean up untidy corners ... Clipper clpr = new Clipper(); clpr.AddPaths(solution, PolyType.ptSubject, true); if (delta > 0) { clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftPositive, PolyFillType.pftPositive); } else { IntRect r = clpr.GetBounds(); Path outer = new Path(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.AddPath(outer, PolyType.ptSubject, true); clpr.ReverseSolution = true; clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftNegative, PolyFillType.pftNegative); if (solution.Count > 0) solution.RemoveAt(0); } }