// rest of the code doesn't care about point format // basic distance-based simplification public static NFP simplifyRadialDist(NFP points, double?sqTolerance) { var prevPoint = points[0]; var newPoints = new NFP(); newPoints.AddPoint(prevPoint); SvgPoint point = null; int i = 1; for (var len = points.length; i < len; i++) { point = points[i]; if (point.marked || getSqDist(point, prevPoint) > sqTolerance) { newPoints.AddPoint(point); prevPoint = point; } } if (prevPoint != point) { newPoints.AddPoint(point); } return(newPoints); }
public void AddPoint(SvgPoint point) { var list = Points.ToList(); list.Add(point); Points = list.ToArray(); }
// square distance from a point to a segment public static double getSqSegDist(SvgPoint p, SvgPoint p1, SvgPoint p2) { var x = p1.x; var y = p1.y; var dx = p2.x - x; var dy = p2.y - y; if (dx != 0 || dy != 0) { var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy); if (t > 1) { x = p2.x; y = p2.y; } else if (t > 0) { x += dx * t; y += dy * t; } } dx = p.x - x; dy = p.y - y; return(dx * dx + dy * dy); }
// to suit your point format, run search/replace for '.x' and '.y'; // for 3D version, see 3d branch (configurability would draw significant performance overhead) // square distance between 2 points public static double getSqDist(SvgPoint p1, SvgPoint p2) { var dx = p1.x - p2.x; var dy = p1.y - p2.y; return(dx * dx + dy * dy); }
public void Rebuild() { Points = new SvgPoint[] { }; AddPoint(new SvgPoint(x, y)); AddPoint(new SvgPoint(x + Width, y)); AddPoint(new SvgPoint(x + Width, y + Height)); AddPoint(new SvgPoint(x, y + Height)); }
public static bool pointInPolygon(SvgPoint point, NFP polygon) { // scaling is deliberately coarse to filter out points that lie *on* the polygon var p = svgToClipper2(polygon, 1000); var pt = new ClipperLib.IntPoint(1000 * point.x, 1000 * point.y); return(ClipperLib.Clipper.PointInPolygon(pt, p.ToList()) > 0); }
public static SvgPoint getTarget(SvgPoint o, NFP simple, double tol) { List <InrangeItem> inrange = new List <InrangeItem>(); // find closest points within 2 offset deltas for (var j = 0; j < simple.length; j++) { var s = simple[j]; var d2 = (o.x - s.x) * (o.x - s.x) + (o.y - s.y) * (o.y - s.y); if (d2 < tol * tol) { inrange.Add(new InrangeItem() { point = s, distance = d2 }); } } SvgPoint target = null; if (inrange.Count > 0) { var filtered = inrange.Where((p) => { return(p.point.exact); }).ToList(); // use exact points when available, normal points when not inrange = filtered.Count > 0 ? filtered : inrange; inrange = inrange.OrderBy((b) => { return(b.distance); }).ToList(); target = inrange[0].point; } else { double?mind = null; for (int j = 0; j < simple.length; j++) { var s = simple[j]; var d2 = (o.x - s.x) * (o.x - s.x) + (o.y - s.y) * (o.y - s.y); if (mind == null || d2 < mind) { target = s; mind = d2; } } } return(target); }
public static int?find(SvgPoint v, NFP p) { for (var i = 0; i < p.length; i++) { if (GeometryUtil._withinDistance(v, p[i], Config.curveTolerance / 1000)) { return(i); } } return(null); }
internal void push(SvgPoint svgPoint) { List <SvgPoint> points = new List <SvgPoint>(); if (Points == null) { Points = new SvgPoint[] { }; } points.AddRange(Points); points.Add(svgPoint); Points = points.ToArray(); }
public NFP() { Points = new SvgPoint[] { }; }
public static double DistTo(this SvgPoint p, SvgPoint p2) { return(Math.Sqrt(Math.Pow(p.x - p2.x, 2) + Math.Pow(p.y - p2.y, 2))); }
public static NFP simplifyFunction(NFP polygon, bool inside) { var tolerance = 4 * Config.curveTolerance; // give special treatment to line segments above this length (squared) var fixedTolerance = 40 * Config.curveTolerance * 40 * Config.curveTolerance; int i, j, k; if (Config.simplify) { /* * // use convex hull * var hull = new ConvexHullGrahamScan(); * for(var i=0; i<polygon.length; i++){ * hull.addPoint(polygon[i].x, polygon[i].y); * } * * return hull.getHull();*/ var hull = Background.getHull(polygon); if (hull != null) { return(hull); } else { return(polygon); } } var cleaned = cleanPolygon2(polygon); 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); // simplification process reduced poly to a line or point if (simple == null) { simple = polygon; } var offsets = polygonOffsetDeepNest(simple, inside ? -tolerance : tolerance); 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); 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, Config.curveTolerance / 1000) || !GeometryUtil._withinDistance(p2, s2, Config.curveTolerance / 1000))) { p1.x = s1.x; p1.y = s1.y; p2.x = s2.x; p2.y = s2.y; straightened = true; } } } //if(straightened){ var Ac = _Clipper.ScaleUpPaths(offset, 10000000); var Bc = _Clipper.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 = Background.toNestCoordinates(combined[i].ToArray(), 10000000); var sarea = -GeometryUtil.polygonArea(n); if (largestArea == null || largestArea < sarea) { offset = n; largestArea = sarea; } } } //} cleaned = cleanPolygon2(offset); 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.children = holes; } return(offset); }
public static SvgPoint RotatePoint(SvgPoint p, double cx, double cy, double angle) { return(new SvgPoint(Math.Cos(angle) * (p.x - cx) - Math.Sin(angle) * (p.y - cy) + cx, Math.Sin(angle) * (p.x - cx) + Math.Cos(angle) * (p.y - cy) + cy)); }