/******************************************************************************* * Author : Angus Johnson * * Date : 27 January 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #ifndef CLIPPER_H #define CLIPPER_H #include #include #include "clipper.core.h" #include "clipper.engine.h" #include "clipper.offset.h" #include "clipper.minkowski.h" #include "clipper.rectclip.h" namespace Clipper2Lib { inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule, const Paths64& subjects, const Paths64& clips) { Paths64 result; Clipper64 clipper; clipper.AddSubject(subjects); clipper.AddClip(clips); clipper.Execute(cliptype, fillrule, result); return result; } inline void BooleanOp(ClipType cliptype, FillRule fillrule, const Paths64& subjects, const Paths64& clips, PolyTree64& solution) { Paths64 sol_open; Clipper64 clipper; clipper.AddSubject(subjects); clipper.AddClip(clips); clipper.Execute(cliptype, fillrule, solution, sol_open); } inline PathsD BooleanOp(ClipType cliptype, FillRule fillrule, const PathsD& subjects, const PathsD& clips, int decimal_prec = 2) { CheckPrecision(decimal_prec); PathsD result; ClipperD clipper(decimal_prec); clipper.AddSubject(subjects); clipper.AddClip(clips); clipper.Execute(cliptype, fillrule, result); return result; } inline void BooleanOp(ClipType cliptype, FillRule fillrule, const PathsD& subjects, const PathsD& clips, PolyTreeD& polytree, int decimal_prec = 2) { CheckPrecision(decimal_prec); PathsD result; ClipperD clipper(decimal_prec); clipper.AddSubject(subjects); clipper.AddClip(clips); clipper.Execute(cliptype, fillrule, polytree); } inline Paths64 Intersect(const Paths64& subjects, const Paths64& clips, FillRule fillrule) { return BooleanOp(ClipType::Intersection, fillrule, subjects, clips); } inline PathsD Intersect(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2) { return BooleanOp(ClipType::Intersection, fillrule, subjects, clips, decimal_prec); } inline Paths64 Union(const Paths64& subjects, const Paths64& clips, FillRule fillrule) { return BooleanOp(ClipType::Union, fillrule, subjects, clips); } inline PathsD Union(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2) { return BooleanOp(ClipType::Union, fillrule, subjects, clips, decimal_prec); } inline Paths64 Union(const Paths64& subjects, FillRule fillrule) { Paths64 result; Clipper64 clipper; clipper.AddSubject(subjects); clipper.Execute(ClipType::Union, fillrule, result); return result; } inline PathsD Union(const PathsD& subjects, FillRule fillrule, int decimal_prec = 2) { CheckPrecision(decimal_prec); PathsD result; ClipperD clipper(decimal_prec); clipper.AddSubject(subjects); clipper.Execute(ClipType::Union, fillrule, result); return result; } inline Paths64 Difference(const Paths64& subjects, const Paths64& clips, FillRule fillrule) { return BooleanOp(ClipType::Difference, fillrule, subjects, clips); } inline PathsD Difference(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2) { return BooleanOp(ClipType::Difference, fillrule, subjects, clips, decimal_prec); } inline Paths64 Xor(const Paths64& subjects, const Paths64& clips, FillRule fillrule) { return BooleanOp(ClipType::Xor, fillrule, subjects, clips); } inline PathsD Xor(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2) { return BooleanOp(ClipType::Xor, fillrule, subjects, clips, decimal_prec); } inline Paths64 InflatePaths(const Paths64& paths, double delta, JoinType jt, EndType et, double miter_limit = 2.0) { ClipperOffset clip_offset(miter_limit); clip_offset.AddPaths(paths, jt, et); return clip_offset.Execute(delta); } inline PathsD InflatePaths(const PathsD& paths, double delta, JoinType jt, EndType et, double miter_limit = 2.0, int precision = 2) { CheckPrecision(precision); const double scale = std::pow(10, precision); ClipperOffset clip_offset(miter_limit); clip_offset.AddPaths(ScalePaths(paths, scale), jt, et); Paths64 tmp = clip_offset.Execute(delta * scale); return ScalePaths(tmp, 1 / scale); } inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy) { Path64 result; result.reserve(path.size()); std::transform(path.begin(), path.end(), back_inserter(result), [dx, dy](const auto& pt) { return Point64(pt.x + dx, pt.y +dy); }); return result; } inline PathD TranslatePath(const PathD& path, double dx, double dy) { PathD result; result.reserve(path.size()); std::transform(path.begin(), path.end(), back_inserter(result), [dx, dy](const auto& pt) { return PointD(pt.x + dx, pt.y + dy); }); return result; } inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy) { Paths64 result; result.reserve(paths.size()); std::transform(paths.begin(), paths.end(), back_inserter(result), [dx, dy](const auto& path) { return TranslatePath(path, dx, dy); }); return result; } inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy) { PathsD result; result.reserve(paths.size()); std::transform(paths.begin(), paths.end(), back_inserter(result), [dx, dy](const auto& path) { return TranslatePath(path, dx, dy); }); return result; } inline Path64 RectClip(const Rect64& rect, const Path64& path) { if (rect.IsEmpty() || path.empty()) return Path64(); Rect64 pathRec = Bounds(path); if (!rect.Intersects(pathRec)) return Path64(); if (rect.Contains(pathRec)) return path; class RectClip rc(rect); return rc.Execute(path); } inline Paths64 RectClip(const Rect64& rect, const Paths64& paths) { if (rect.IsEmpty() || paths.empty()) return Paths64(); class RectClip rc(rect); Paths64 result; result.reserve(paths.size()); for (const Path64& p : paths) { Rect64 pathRec = Bounds(p); if (!rect.Intersects(pathRec)) continue; else if (rect.Contains(pathRec)) result.push_back(p); else { Path64 p2 = rc.Execute(p); if (!p2.empty()) result.push_back(std::move(p2)); } } return result; } inline PathD RectClip(const RectD& rect, const PathD& path, int precision = 2) { if (rect.IsEmpty() || path.empty() || !rect.Contains(Bounds(path))) return PathD(); CheckPrecision(precision); const double scale = std::pow(10, precision); Rect64 r = ScaleRect(rect, scale); class RectClip rc(r); Path64 p = ScalePath(path, scale); return ScalePath(rc.Execute(p), 1 / scale); } inline PathsD RectClip(const RectD& rect, const PathsD& paths, int precision = 2) { if (rect.IsEmpty() || paths.empty()) return PathsD(); CheckPrecision(precision); const double scale = std::pow(10, precision); Rect64 r = ScaleRect(rect, scale); class RectClip rc(r); PathsD result; result.reserve(paths.size()); for (const PathD& path : paths) { RectD pathRec = Bounds(path); if (!rect.Intersects(pathRec)) continue; else if (rect.Contains(pathRec)) result.push_back(path); else { Path64 p = ScalePath(path, scale); p = rc.Execute(p); if (!p.empty()) result.push_back(ScalePath(p, 1 / scale)); } } return result; } inline Paths64 RectClipLines(const Rect64& rect, const Path64& path) { Paths64 result; if (rect.IsEmpty() || path.empty()) return result; Rect64 pathRec = Bounds(path); if (!rect.Intersects(pathRec)) return result; if (rect.Contains(pathRec)) { result.push_back(path); return result; } class RectClipLines rcl(rect); return rcl.Execute(path); } inline Paths64 RectClipLines(const Rect64& rect, const Paths64& paths) { Paths64 result; if (rect.IsEmpty() || paths.empty()) return result; class RectClipLines rcl(rect); for (const Path64& p : paths) { Rect64 pathRec = Bounds(p); if (!rect.Intersects(pathRec)) continue; else if (rect.Contains(pathRec)) result.push_back(p); else { Paths64 pp = rcl.Execute(p); if (!pp.empty()) result.insert(result.end(), pp.begin(), pp.end()); } } return result; } inline PathsD RectClipLines(const RectD& rect, const PathD& path, int precision = 2) { if (rect.IsEmpty() || path.empty() || !rect.Contains(Bounds(path))) return PathsD(); CheckPrecision(precision); const double scale = std::pow(10, precision); Rect64 r = ScaleRect(rect, scale); class RectClipLines rcl(r); Path64 p = ScalePath(path, scale); return ScalePaths(rcl.Execute(p), 1 / scale); } inline PathsD RectClipLines(const RectD& rect, const PathsD& paths, int precision = 2) { PathsD result; if (rect.IsEmpty() || paths.empty()) return result; CheckPrecision(precision); const double scale = std::pow(10, precision); Rect64 r = ScaleRect(rect, scale); class RectClipLines rcl(r); result.reserve(paths.size()); for (const PathD& path : paths) { RectD pathRec = Bounds(path); if (!rect.Intersects(pathRec)) continue; else if (rect.Contains(pathRec)) result.push_back(path); else { Path64 p = ScalePath(path, scale); Paths64 pp = rcl.Execute(p); if (pp.empty()) continue; PathsD ppd = ScalePaths(pp, 1 / scale); result.insert(result.end(), ppd.begin(), ppd.end()); } } return result; } namespace details { inline void PolyPathToPaths64(const PolyPath64& polypath, Paths64& paths) { paths.push_back(polypath.Polygon()); for (const auto& child : polypath) PolyPathToPaths64(*child, paths); } inline void PolyPathToPathsD(const PolyPathD& polypath, PathsD& paths) { paths.push_back(polypath.Polygon()); for (const auto& child : polypath) PolyPathToPathsD(*child, paths); } inline bool PolyPath64ContainsChildren(const PolyPath64& pp) { for (const auto& child : pp) { // return false if this child isn't fully contained by its parent // the following algorithm is a bit too crude, and doesn't account // for rounding errors. A better algorithm is to return false when // consecutive vertices are found outside the parent's polygon. //const Path64& path = pp.Polygon(); //if (std::any_of(child->Polygon().cbegin(), child->Polygon().cend(), // [path](const auto& pt) {return (PointInPolygon(pt, path) == // PointInPolygonResult::IsOutside); })) return false; int outsideCnt = 0; for (const Point64& pt : child->Polygon()) { PointInPolygonResult result = PointInPolygon(pt, pp.Polygon()); if (result == PointInPolygonResult::IsInside) --outsideCnt; else if (result == PointInPolygonResult::IsOutside) ++outsideCnt; if (outsideCnt > 1) return false; else if (outsideCnt < -1) break; } // now check any nested children too if (child->Count() > 0 && !PolyPath64ContainsChildren(*child)) return false; } return true; } inline bool GetInt(std::string::const_iterator& iter, const std::string::const_iterator& end_iter, int64_t& val) { val = 0; bool is_neg = *iter == '-'; if (is_neg) ++iter; std::string::const_iterator start_iter = iter; while (iter != end_iter && ((*iter >= '0') && (*iter <= '9'))) { val = val * 10 + (static_cast(*iter++) - '0'); } if (is_neg) val = -val; return (iter != start_iter); } inline bool GetFloat(std::string::const_iterator& iter, const std::string::const_iterator& end_iter, double& val) { val = 0; bool is_neg = *iter == '-'; if (is_neg) ++iter; int dec_pos = 1; const std::string::const_iterator start_iter = iter; while (iter != end_iter && (*iter == '.' || ((*iter >= '0') && (*iter <= '9')))) { if (*iter == '.') { if (dec_pos != 1) break; dec_pos = 0; ++iter; continue; } if (dec_pos != 1) --dec_pos; val = val * 10 + ((int64_t)(*iter++) - '0'); } if (iter == start_iter || dec_pos == 0) return false; if (dec_pos < 0) val *= std::pow(10, dec_pos); if (is_neg) val *= -1; return true; } inline void SkipWhiteSpace(std::string::const_iterator& iter, const std::string::const_iterator& end_iter) { while (iter != end_iter && *iter <= ' ') ++iter; } inline void SkipSpacesWithOptionalComma(std::string::const_iterator& iter, const std::string::const_iterator& end_iter) { bool comma_seen = false; while (iter != end_iter) { if (*iter == ' ') ++iter; else if (*iter == ',') { if (comma_seen) return; // don't skip 2 commas! comma_seen = true; ++iter; } else return; } } inline bool has_one_match(const char c, char* chrs) { while (*chrs > 0 && c != *chrs) ++chrs; if (!*chrs) return false; *chrs = ' '; // only match once per char return true; } inline void SkipUserDefinedChars(std::string::const_iterator& iter, const std::string::const_iterator& end_iter, const std::string& skip_chars) { const size_t MAX_CHARS = 16; char buff[MAX_CHARS] = {0}; std::copy(skip_chars.cbegin(), skip_chars.cend(), &buff[0]); while (iter != end_iter && (*iter <= ' ' || has_one_match(*iter, buff))) ++iter; return; } static void OutlinePolyPath(std::ostream& os, bool isHole, size_t count, const std::string& preamble) { std::string plural = (count == 1) ? "." : "s."; if (isHole) { if (count) os << preamble << "+- Hole with " << count << " nested polygon" << plural << std::endl; else os << preamble << "+- Hole" << std::endl; } else { if (count) os << preamble << "+- Polygon with " << count << " hole" << plural << std::endl; else os << preamble << "+- Polygon" << std::endl; } } static void OutlinePolyPath64(std::ostream& os, const PolyPath64& pp, std::string preamble, bool last_child) { OutlinePolyPath(os, pp.IsHole(), pp.Count(), preamble); preamble += (!last_child) ? "| " : " "; if (pp.Count()) { PolyPath64List::const_iterator it = pp.begin(); for (; it < pp.end() - 1; ++it) OutlinePolyPath64(os, **it, preamble, false); OutlinePolyPath64(os, **it, preamble, true); } } static void OutlinePolyPathD(std::ostream& os, const PolyPathD& pp, std::string preamble, bool last_child) { OutlinePolyPath(os, pp.IsHole(), pp.Count(), preamble); preamble += (!last_child) ? "| " : " "; if (pp.Count()) { PolyPathDList::const_iterator it = pp.begin(); for (; it < pp.end() - 1; ++it) OutlinePolyPathD(os, **it, preamble, false); OutlinePolyPathD(os, **it, preamble, true); } } } // end details namespace inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp) { PolyPath64List::const_iterator it = pp.begin(); for (; it < pp.end() - 1; ++it) details::OutlinePolyPath64(os, **it, " ", false); details::OutlinePolyPath64(os, **it, " ", true); os << std::endl << std::endl; if (!pp.Level()) os << std::endl; return os; } inline std::ostream& operator<< (std::ostream& os, const PolyTreeD& pp) { PolyPathDList::const_iterator it = pp.begin(); for (; it < pp.end() - 1; ++it) details::OutlinePolyPathD(os, **it, " ", false); details::OutlinePolyPathD(os, **it, " ", true); os << std::endl << std::endl; if (!pp.Level()) os << std::endl; return os; } inline Paths64 PolyTreeToPaths64(const PolyTree64& polytree) { Paths64 result; for (const auto& child : polytree) details::PolyPathToPaths64(*child, result); return result; } inline PathsD PolyTreeToPathsD(const PolyTreeD& polytree) { PathsD result; for (const auto& child : polytree) details::PolyPathToPathsD(*child, result); return result; } inline bool CheckPolytreeFullyContainsChildren(const PolyTree64& polytree) { for (const auto& child : polytree) if (child->Count() > 0 && !details::PolyPath64ContainsChildren(*child)) return false; return true; } inline Path64 MakePath(const std::string& s) { const std::string skip_chars = " ,(){}[]"; Path64 result; std::string::const_iterator s_iter = s.cbegin(); details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars); while (s_iter != s.cend()) { int64_t y = 0, x = 0; if (!details::GetInt(s_iter, s.cend(), x)) break; details::SkipSpacesWithOptionalComma(s_iter, s.cend()); if (!details::GetInt(s_iter, s.cend(), y)) break; result.push_back(Point64(x, y)); details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars); } return result; } inline PathD MakePathD(const std::string& s) { const std::string skip_chars = " ,(){}[]"; PathD result; std::string::const_iterator s_iter = s.cbegin(); details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars); while (s_iter != s.cend()) { double y = 0, x = 0; if (!details::GetFloat(s_iter, s.cend(), x)) break; details::SkipSpacesWithOptionalComma(s_iter, s.cend()); if (!details::GetFloat(s_iter, s.cend(), y)) break; result.push_back(PointD(x, y)); details::SkipUserDefinedChars(s_iter, s.cend(), skip_chars); } return result; } inline Path64 TrimCollinear(const Path64& p, bool is_open_path = false) { size_t len = p.size(); if (len < 3) { if (!is_open_path || len < 2 || p[0] == p[1]) return Path64(); else return p; } Path64 dst; dst.reserve(len); Path64::const_iterator srcIt = p.cbegin(), prevIt, stop = p.cend() - 1; if (!is_open_path) { while (srcIt != stop && !CrossProduct(*stop, *srcIt, *(srcIt + 1))) ++srcIt; while (srcIt != stop && !CrossProduct(*(stop - 1), *stop, *srcIt)) --stop; if (srcIt == stop) return Path64(); } prevIt = srcIt++; dst.push_back(*prevIt); for (; srcIt != stop; ++srcIt) { if (CrossProduct(*prevIt, *srcIt, *(srcIt + 1))) { prevIt = srcIt; dst.push_back(*prevIt); } } if (is_open_path) dst.push_back(*srcIt); else if (CrossProduct(*prevIt, *stop, dst[0])) dst.push_back(*stop); else { while (dst.size() > 2 && !CrossProduct(dst[dst.size() - 1], dst[dst.size() - 2], dst[0])) dst.pop_back(); if (dst.size() < 3) return Path64(); } return dst; } inline PathD TrimCollinear(const PathD& path, int precision, bool is_open_path = false) { CheckPrecision(precision); const double scale = std::pow(10, precision); Path64 p = ScalePath(path, scale); p = TrimCollinear(p, is_open_path); return ScalePath(p, 1/scale); } template inline double Distance(const Point pt1, const Point pt2) { return std::sqrt(DistanceSqr(pt1, pt2)); } template inline double Length(const Path& path, bool is_closed_path = false) { double result = 0.0; if (path.size() < 2) return result; auto it = path.cbegin(), stop = path.end() - 1; for (; it != stop; ++it) result += Distance(*it, *(it + 1)); if (is_closed_path) result += Distance(*stop, *path.cbegin()); return result; } template inline bool NearCollinear(const Point& pt1, const Point& pt2, const Point& pt3, double sin_sqrd_min_angle_rads) { double cp = std::abs(CrossProduct(pt1, pt2, pt3)); return (cp * cp) / (DistanceSqr(pt1, pt2) * DistanceSqr(pt2, pt3)) < sin_sqrd_min_angle_rads; } template inline Path Ellipse(const Rect& rect, int steps = 0) { return Ellipse(rect.MidPoint(), static_cast(rect.Width()) *0.5, static_cast(rect.Height()) * 0.5, steps); } template inline Path Ellipse(const Point& center, double radiusX, double radiusY = 0, int steps = 0) { if (radiusX <= 0) return Path(); if (radiusY <= 0) radiusY = radiusX; if (steps <= 2) steps = static_cast(PI * sqrt((radiusX + radiusY) / 2)); double si = std::sin(2 * PI / steps); double co = std::cos(2 * PI / steps); double dx = co, dy = si; Path result; result.reserve(steps); result.push_back(Point(center.x + radiusX, static_cast(center.y))); for (int i = 1; i < steps; ++i) { result.push_back(Point(center.x + radiusX * dx, center.y + radiusY * dy)); double x = dx * co - dy * si; dy = dy * co + dx * si; dx = x; } return result; } template inline double PerpendicDistFromLineSqrd(const Point& pt, const Point& line1, const Point& line2) { double a = static_cast(pt.x - line1.x); double b = static_cast(pt.y - line1.y); double c = static_cast(line2.x - line1.x); double d = static_cast(line2.y - line1.y); if (c == 0 && d == 0) return 0; return Sqr(a * d - c * b) / (c * c + d * d); } inline size_t GetNext(size_t current, size_t high, const std::vector& flags) { ++current; while (current <= high && flags[current]) ++current; if (current <= high) return current; current = 0; while (flags[current]) ++current; return current; } inline size_t GetPrior(size_t current, size_t high, const std::vector& flags) { if (current == 0) current = high; else --current; while (current > 0 && flags[current]) --current; if (!flags[current]) return current; current = high; while (flags[current]) --current; return current; } template inline Path SimplifyPath(const Path path, double epsilon, bool isOpenPath = false) { const size_t len = path.size(), high = len -1; const double epsSqr = Sqr(epsilon); if (len < 4) return Path(path); std::vector flags(len); std::vector distSqr(len); size_t prior = high, curr = 0, start, next, prior2, next2; if (isOpenPath) { distSqr[0] = MAX_DBL; distSqr[high] = MAX_DBL; } else { distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]); distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]); } for (size_t i = 1; i < high; ++i) distSqr[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]); for (;;) { if (distSqr[curr] > epsSqr) { start = curr; do { curr = GetNext(curr, high, flags); } while (curr != start && distSqr[curr] > epsSqr); if (curr == start) break; } prior = GetPrior(curr, high, flags); next = GetNext(curr, high, flags); if (next == prior) break; if (distSqr[next] < distSqr[curr]) { flags[next] = true; next = GetNext(next, high, flags); next2 = GetNext(next, high, flags); distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); if (next != high || !isOpenPath) distSqr[next] = PerpendicDistFromLineSqrd(path[next], path[curr], path[next2]); curr = next; } else { flags[curr] = true; curr = next; next = GetNext(next, high, flags); prior2 = GetPrior(prior, high, flags); distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); if (prior != 0 || !isOpenPath) distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]); } } Path result; result.reserve(len); for (typename Path::size_type i = 0; i < len; ++i) if (!flags[i]) result.push_back(path[i]); return result; } template inline Paths SimplifyPaths(const Paths paths, double epsilon, bool isOpenPath = false) { Paths result; result.reserve(paths.size()); for (const auto& path : paths) result.push_back(SimplifyPath(path, epsilon, isOpenPath)); return result; } template inline void RDP(const Path path, std::size_t begin, std::size_t end, double epsSqrd, std::vector& flags) { typename Path::size_type idx = 0; double max_d = 0; while (end > begin && path[begin] == path[end]) flags[end--] = false; for (typename Path::size_type i = begin + 1; i < end; ++i) { // PerpendicDistFromLineSqrd - avoids expensive Sqrt() double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]); if (d <= max_d) continue; max_d = d; idx = i; } if (max_d <= epsSqrd) return; flags[idx] = true; if (idx > begin + 1) RDP(path, begin, idx, epsSqrd, flags); if (idx < end - 1) RDP(path, idx, end, epsSqrd, flags); } template inline Path RamerDouglasPeucker(const Path& path, double epsilon) { const typename Path::size_type len = path.size(); if (len < 5) return Path(path); std::vector flags(len); flags[0] = true; flags[len - 1] = true; RDP(path, 0, len - 1, Sqr(epsilon), flags); Path result; result.reserve(len); for (typename Path::size_type i = 0; i < len; ++i) if (flags[i]) result.push_back(path[i]); return result; } template inline Paths RamerDouglasPeucker(const Paths& paths, double epsilon) { Paths result; result.reserve(paths.size()); std::transform(paths.begin(), paths.end(), back_inserter(result), [epsilon](const auto& path) { return RamerDouglasPeucker(path, epsilon); }); return result; } } // end Clipper2Lib namespace #endif // CLIPPER_H