public static NFP clone2(NFP nfp) { NFP newnfp = new NFP(); for (var i = 0; i < nfp.Length; i++) { newnfp.AddPoint(new SvgPoint(nfp[i].X, nfp[i].Y)); } if (nfp.Childrens != null && nfp.Childrens.Count > 0) { newnfp.Childrens = new List <NFP>(); for (int i = 0; i < nfp.Childrens.Count; i++) { var child = nfp.Childrens[i]; NFP newchild = new NFP(); for (var j = 0; j < child.Length; j++) { newchild.AddPoint(new SvgPoint(child[j].X, child[j].Y)); } newnfp.Childrens.Add(newchild); } } return(newnfp); }
public static NFP clone(NFP p) { var newp = new NFP(); for (var i = 0; i < p.Length; i++) { newp.AddPoint(new SvgPoint( p[i].X, p[i].Y )); } return(newp); }
public static NFP getHull(NFP polygon) { double[][] points = new double[polygon.Length][]; for (var i = 0; i < polygon.Length; i++) { points[i] = (new double[] { polygon[i].X, polygon[i].Y }); } var hullpoints = polygonHull(points); if (hullpoints == null) { return(polygon); } NFP hull = new NFP(); for (int i = 0; i < hullpoints.Count(); i++) { hull.AddPoint(new SvgPoint(hullpoints[i][0], hullpoints[i][1])); } return(hull); }
public static NFP simplifyFunction(NFP polygon, bool inside, double clipperScale, double curveTolerance = 0.72, bool hullSimplify = false) { var tolerance = 4 * curveTolerance; // give special treatment to line segments above this length (squared) var fixedTolerance = 40 * curveTolerance * 40 * curveTolerance; int i, j, k; if (hullSimplify) { // use convex hull var hull = getHull(polygon); if (hull != null) { return(hull); } else { return(polygon); } } var cleaned = cleanPolygon2(polygon, clipperScale); if (cleaned != null && cleaned.Length > 1) { polygon = cleaned; } else { return(polygon); } // polygon to polyline var copy = polygon.slice(0); copy.push(copy[0]); // mark all segments greater than ~0.25 in to be kept // the PD simplification algo doesn't care about the accuracy of long lines, only the absolute distance of each point // we care a great deal for (i = 0; i < copy.Length - 1; i++) { var p1 = copy[i]; var p2 = copy[i + 1]; var sqd = (p2.X - p1.X) * (p2.X - p1.X) + (p2.Y - p1.Y) * (p2.Y - p1.Y); if (sqd > fixedTolerance) { p1.marked = true; p2.marked = true; } } var simple = Simplify.simplify(copy, tolerance, true); // now a polygon again //simple.pop(); simple.Points = simple.Points.Take(simple.Points.Count() - 1).ToArray(); // could be dirty again (self intersections and/or coincident points) simple = cleanPolygon2(simple, clipperScale); // simplification process reduced poly to a line or point if (simple == null) { simple = polygon; } var offsets = polygonOffsetDeepNest(simple, inside ? -tolerance : tolerance, clipperScale); NFP offset = null; double offsetArea = 0; List <NFP> holes = new List <NFP>(); for (i = 0; i < offsets.Length; i++) { var area = GeometryUtil.polygonArea(offsets[i]); if (offset == null || area < offsetArea) { offset = offsets[i]; offsetArea = area; } if (area > 0) { holes.Add(offsets[i]); } } // mark any points that are exact for (i = 0; i < simple.Length; i++) { var seg = new NFP(); seg.AddPoint(simple[i]); seg.AddPoint(simple[i + 1 == simple.Length ? 0 : i + 1]); var index1 = find(seg[0], polygon); var index2 = find(seg[1], polygon); if (index1 + 1 == index2 || index2 + 1 == index1 || (index1 == 0 && index2 == polygon.Length - 1) || (index2 == 0 && index1 == polygon.Length - 1)) { seg[0].exact = true; seg[1].exact = true; } } var numshells = 4; NFP[] shells = new NFP[numshells]; for (j = 1; j < numshells; j++) { var delta = j * (tolerance / numshells); delta = inside ? -delta : delta; var shell = polygonOffsetDeepNest(simple, delta, clipperScale); if (shell.Count() > 0) { shells[j] = shell.First(); } else { //shells[j] = shell; } } if (offset == null) { return(polygon); } // selective reversal of offset for (i = 0; i < offset.Length; i++) { var o = offset[i]; var target = getTarget(o, simple, 2 * tolerance); // reverse point offset and try to find exterior points var test = clone(offset); test.Points[i] = new SvgPoint(target.X, target.Y); if (!exterior(test, polygon, inside)) { o.X = target.X; o.Y = target.Y; } else { // a shell is an intermediate offset between simple and offset for (j = 1; j < numshells; j++) { if (shells[j] != null) { var shell = shells[j]; var delta = j * (tolerance / numshells); target = getTarget(o, shell, 2 * delta); test = clone(offset); test.Points[i] = new SvgPoint(target.X, target.Y); if (!exterior(test, polygon, inside)) { o.X = target.X; o.Y = target.Y; break; } } } } } // straighten long lines // a rounded rectangle would still have issues at this point, as the long sides won't line up straight var straightened = false; for (i = 0; i < offset.Length; i++) { var p1 = offset[i]; var p2 = offset[i + 1 == offset.Length ? 0 : i + 1]; var sqd = (p2.X - p1.X) * (p2.X - p1.X) + (p2.Y - p1.Y) * (p2.Y - p1.Y); if (sqd < fixedTolerance) { continue; } for (j = 0; j < simple.Length; j++) { var s1 = simple[j]; var s2 = simple[j + 1 == simple.Length ? 0 : j + 1]; var sqds = (p2.X - p1.X) * (p2.X - p1.X) + (p2.Y - p1.Y) * (p2.Y - p1.Y); if (sqds < fixedTolerance) { continue; } if ((GeometryUtil._almostEqual(s1.X, s2.X) || GeometryUtil._almostEqual(s1.Y, s2.Y)) && // we only really care about vertical and horizontal lines GeometryUtil._withinDistance(p1, s1, 2 * tolerance) && GeometryUtil._withinDistance(p2, s2, 2 * tolerance) && (!GeometryUtil._withinDistance(p1, s1, curveTolerance / 1000) || !GeometryUtil._withinDistance(p2, s2, curveTolerance / 1000))) { p1.X = s1.X; p1.Y = s1.Y; p2.X = s2.X; p2.Y = s2.Y; straightened = true; } } } //if(straightened){ var Ac = ClipperHelper.ScaleUpPaths(offset, 10000000); var Bc = ClipperHelper.ScaleUpPaths(polygon, 10000000); var combined = new List <List <IntPoint> >(); var clipper = new ClipperLib.Clipper(); clipper.AddPath(Ac.ToList(), ClipperLib.PolyType.ptSubject, true); clipper.AddPath(Bc.ToList(), ClipperLib.PolyType.ptSubject, true); // the line straightening may have made the offset smaller than the simplified if (clipper.Execute(ClipperLib.ClipType.ctUnion, combined, ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNonZero)) { double?largestArea = null; for (i = 0; i < combined.Count; i++) { var n = toNestCoordinates(combined[i].ToArray(), 10000000); var sarea = -GeometryUtil.polygonArea(n); if (largestArea == null || largestArea < sarea) { offset = n; largestArea = sarea; } } } //} cleaned = cleanPolygon2(offset, clipperScale); if (cleaned != null && cleaned.Length > 1) { offset = cleaned; } // mark any points that are exact (for line merge detection) for (i = 0; i < offset.Length; i++) { var seg = new SvgPoint[] { offset[i], offset[i + 1 == offset.Length ? 0 : i + 1] }; var index1 = find(seg[0], polygon); var index2 = find(seg[1], polygon); if (index1 == null) { index1 = 0; } if (index2 == null) { index2 = 0; } if (index1 + 1 == index2 || index2 + 1 == index1 || (index1 == 0 && index2 == polygon.Length - 1) || (index2 == 0 && index1 == polygon.Length - 1)) { seg[0].exact = true; seg[1].exact = true; } } if (!inside && holes != null && holes.Count > 0) { offset.Childrens = holes; } return(offset); }