diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index bcfbe4a1..0d9ec7b1 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 4 October 2023 * +* Date : 1 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library structures and functions * @@ -19,7 +19,7 @@ #include #include #include -#include "clipper.version.h" +#include "clipper2/clipper.version.h" namespace Clipper2Lib { @@ -229,6 +229,14 @@ namespace Clipper2Lib using Paths64 = std::vector< Path64>; using PathsD = std::vector< PathD>; + static const Point64 InvalidPoint64 = Point64( + (std::numeric_limits::max)(), + (std::numeric_limits::max)()); + static const PointD InvalidPointD = PointD( + (std::numeric_limits::max)(), + (std::numeric_limits::max)()); + + // Rect ------------------------------------------------------------------------ template @@ -259,10 +267,12 @@ namespace Clipper2Lib else { left = top = (std::numeric_limits::max)(); - right = bottom = -(std::numeric_limits::max)(); + right = bottom = (std::numeric_limits::lowest)(); } } + bool IsValid() const { return left != (std::numeric_limits::max)(); } + T Width() const { return right - left; } T Height() const { return bottom - top; } void Width(T width) { right = left + width; } @@ -344,10 +354,16 @@ namespace Clipper2Lib return result; } - static const Rect64 MaxInvalidRect64 = Rect64( - INT64_MAX, INT64_MAX, INT64_MIN, INT64_MIN); - static const RectD MaxInvalidRectD = RectD( - MAX_DBL, MAX_DBL, -MAX_DBL, -MAX_DBL); + static const Rect64 InvalidRect64 = Rect64( + (std::numeric_limits::max)(), + (std::numeric_limits::max)(), + (std::numeric_limits::lowest)(), + (std::numeric_limits::lowest)()); + static const RectD InvalidRectD = RectD( + (std::numeric_limits::max)(), + (std::numeric_limits::max)(), + (std::numeric_limits::lowest)(), + (std::numeric_limits::lowest)()); template Rect GetBounds(const Path& path) @@ -549,7 +565,7 @@ namespace Clipper2Lib inline void StripDuplicates( Path& path, bool is_closed_path) { //https://stackoverflow.com/questions/1041620/whats-the-most-efficient-way-to-erase-duplicates-and-sort-a-vector#:~:text=Let%27s%20compare%20three%20approaches%3A - path.erase(std::unique(path.begin(), path.end()),path.end()); + path.erase(std::unique(path.begin(), path.end()), path.end()); if (is_closed_path) while (path.size() > 1 && path.back() == path.front()) path.pop_back(); } diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h index e41fe633..fb95f69d 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h @@ -19,7 +19,7 @@ #include #include -#include "clipper.core.h" +#include "clipper2/clipper.core.h" namespace Clipper2Lib { diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index 8c56c0d5..8e0c7280 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 29 October 2023 * +* Date : 1 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module exports the Clipper2 Library (ie DLL/so) * @@ -138,7 +138,6 @@ EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD& p) delete[] p; } - // Boolean clipping: // cliptype: None=0, Intersection=1, Union=2, Difference=3, Xor=4 // fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3 @@ -226,7 +225,7 @@ static void GetPolytreeCountAndCStorageSize(const PolyTree64& tree, template static T* CreateCPaths(const Paths& paths) { - size_t cnt, array_len; + size_t cnt = 0, array_len = 0; GetPathCountAndCPathsArrayLen(paths, cnt, array_len); T* result = new T[array_len], * v = result; *v++ = array_len; @@ -496,13 +495,15 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, double arc_tolerance, bool reverse_solution) { if (precision < -8 || precision > 8 || !paths) return nullptr; + const double scale = std::pow(10, precision); ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution); Paths64 pp = ConvertCPathsDToPaths64(paths, scale); clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype)); Paths64 result; clip_offset.Execute(delta * scale, result); - return CreateCPathsDFromPaths64(result, 1/scale); + + return CreateCPathsDFromPaths64(result, 1 / scale); } EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths) @@ -526,7 +527,8 @@ EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int Paths64 pp = ConvertCPathsDToPaths64(paths, scale); class RectClip64 rc(rec); Paths64 result = rc.Execute(pp); - return CreateCPathsDFromPaths64(result, 1/scale); + + return CreateCPathsDFromPaths64(result, 1 / scale); } EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, @@ -545,12 +547,13 @@ EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, { if (CRectIsEmpty(rect) || !paths) return nullptr; if (precision < -8 || precision > 8) return nullptr; + const double scale = std::pow(10, precision); Rect64 r = ScaleRect(CRectToRect(rect), scale); class RectClipLines64 rcl(r); Paths64 pp = ConvertCPathsDToPaths64(paths, scale); Paths64 result = rcl.Execute(pp); - return CreateCPathsDFromPaths64(result, 1/scale); + return CreateCPathsDFromPaths64(result, 1 / scale); } } // end Clipper2Lib namespace diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.h b/CPP/Clipper2Lib/include/clipper2/clipper.h index b5ac6aa9..2481d1fb 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 July 2023 * +* Date : 1 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -14,11 +14,11 @@ #include #include -#include "clipper.core.h" -#include "clipper.engine.h" -#include "clipper.offset.h" -#include "clipper.minkowski.h" -#include "clipper.rectclip.h" +#include "clipper2/clipper.core.h" +#include "clipper2/clipper.engine.h" +#include "clipper2/clipper.offset.h" +#include "clipper2/clipper.minkowski.h" +#include "clipper2/clipper.rectclip.h" namespace Clipper2Lib { @@ -653,7 +653,7 @@ namespace Clipper2Lib { } template - inline Path SimplifyPath(const Path path, + inline Path SimplifyPath(const Path &path, double epsilon, bool isClosedPath = true) { const size_t len = path.size(), high = len -1; @@ -721,7 +721,7 @@ namespace Clipper2Lib { } template - inline Paths SimplifyPaths(const Paths paths, + inline Paths SimplifyPaths(const Paths &paths, double epsilon, bool isClosedPath = true) { Paths result; diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.minkowski.h b/CPP/Clipper2Lib/include/clipper2/clipper.minkowski.h index 71c221bb..ebddd08a 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.minkowski.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.minkowski.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 28 January 2023 * +* Date : 1 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Minkowski Sum and Difference * @@ -13,7 +13,7 @@ #include #include #include -#include "clipper.core.h" +#include "clipper2/clipper.core.h" namespace Clipper2Lib { diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.offset.h b/CPP/Clipper2Lib/include/clipper2/clipper.offset.h index e48ae9dc..a1b16b5e 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.offset.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.offset.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 24 September 2023 * +* Date : 5 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -34,13 +34,12 @@ class ClipperOffset { class Group { public: Paths64 paths_in; - Paths64 paths_out; - Path64 path; + std::vector bounds_list; + int lowest_path_idx = -1; bool is_reversed = false; JoinType join_type; EndType end_type; - Group(const Paths64& _paths, JoinType _join_type, EndType _end_type) : - paths_in(_paths), join_type(_join_type), end_type(_end_type) {} + Group(const Paths64& _paths, JoinType _join_type, EndType _end_type); }; int error_code_ = 0; @@ -51,6 +50,7 @@ class ClipperOffset { double step_sin_ = 0.0; double step_cos_ = 0.0; PathD norms; + Path64 path_out; Paths64 solution; std::vector groups_; JoinType join_type_ = JoinType::Bevel; @@ -66,15 +66,17 @@ class ClipperOffset { #endif DeltaCallback64 deltaCallback64_ = nullptr; - void DoBevel(Group& group, const Path64& path, size_t j, size_t k); - void DoSquare(Group& group, const Path64& path, size_t j, size_t k); - void DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a); - void DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle); + size_t CalcSolutionCapacity(); + bool CheckReverseOrientation(); + void DoBevel(const Path64& path, size_t j, size_t k); + void DoSquare(const Path64& path, size_t j, size_t k); + void DoMiter(const Path64& path, size_t j, size_t k, double cos_a); + void DoRound(const Path64& path, size_t j, size_t k, double angle); void BuildNormals(const Path64& path); - void OffsetPolygon(Group& group, Path64& path); - void OffsetOpenJoined(Group& group, Path64& path); - void OffsetOpenPath(Group& group, Path64& path); - void OffsetPoint(Group& group, Path64& path, size_t j, size_t k); + void OffsetPolygon(Group& group, const Path64& path); + void OffsetOpenJoined(Group& group, const Path64& path); + void OffsetOpenPath(Group& group, const Path64& path); + void OffsetPoint(Group& group, const Path64& path, size_t j, size_t k); void DoGroupOffset(Group &group); void ExecuteInternal(double delta); public: diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h b/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h index b6861c61..ff043f25 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 30 May 2023 * +* Date : 1 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -13,7 +13,7 @@ #include #include #include -#include "clipper.core.h" +#include "clipper2/clipper.core.h" namespace Clipper2Lib { diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index 0e1d8de9..14f978ed 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 19 October 2023 * +* Date : 1 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -15,6 +15,7 @@ #include #include "clipper2/clipper.engine.h" +#include "clipper2/clipper.h" // https://github.com/AngusJohnson/Clipper2/discussions/334 // #discussioncomment-4248602 @@ -2140,7 +2141,7 @@ namespace Clipper2Lib { horz_seg_list_.end(), [](HorzSegment& hs) { return UpdateHorzSegment(hs); }); if (j < 2) return; - std::sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter()); + std::stable_sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter()); HorzSegmentList::iterator hs1 = horz_seg_list_.begin(), hs2; HorzSegmentList::iterator hs_end = hs1 +j; diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index 0439ec52..9581a643 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 24 September 2023 * +* Date : 8 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -20,38 +20,59 @@ const double floating_point_tolerance = 1e-12; // Miscellaneous methods //------------------------------------------------------------------------------ -void GetBoundsAndLowestPolyIdx(const Paths64& paths, Rect64& r, int & idx) +void GetMultiBounds(const Paths64& paths, std::vector& recList, EndType end_type) { - idx = -1; - r = MaxInvalidRect64; - int64_t lpx = 0; - for (int i = 0; i < static_cast(paths.size()); ++i) - for (const Point64& p : paths[i]) + size_t min_path_len = (end_type == EndType::Polygon) ? 3 : 1; + recList.reserve(paths.size()); + for (const Path64& path : paths) + { + if (path.size() < min_path_len) { - if (p.y >= r.bottom) - { - if (p.y > r.bottom || p.x < lpx) - { - idx = i; - lpx = p.x; - r.bottom = p.y; - } - } - else if (p.y < r.top) r.top = p.y; - if (p.x > r.right) r.right = p.x; - else if (p.x < r.left) r.left = p.x; + recList.push_back(InvalidRect64); + continue; + } + int64_t x = path[0].x, y = path[0].y; + Rect64 r = Rect64(x, y, x, y); + for (const Point64& pt : path) + { + if (pt.y > r.bottom) r.bottom = pt.y; + else if (pt.y < r.top) r.top = pt.y; + if (pt.x > r.right) r.right = pt.x; + else if (pt.x < r.left) r.left = pt.x; } - //if (idx < 0) r = Rect64(0, 0, 0, 0); - //if (r.top == INT64_MIN) r.bottom = r.top; - //if (r.left == INT64_MIN) r.left = r.right; + recList.push_back(r); + } +} + +bool ValidateBounds(std::vector& recList, double delta) +{ + int64_t int_delta = static_cast(delta); + int64_t big = MAX_COORD - int_delta; + int64_t small = MIN_COORD + int_delta; + for (const Rect64& r : recList) + { + if (!r.IsValid()) continue; // ignore invalid paths + else if (r.left < small || r.right > big || + r.top < small || r.bottom > big) return false; + } + return true; } -bool IsSafeOffset(const Rect64& r, double abs_delta) +int GetLowestClosedPathIdx(std::vector& boundsList) { - return r.left > min_coord + abs_delta && - r.right < max_coord - abs_delta && - r.top > min_coord + abs_delta && - r.bottom < max_coord - abs_delta; + int i = -1, result = -1; + Point64 botPt = Point64(INT64_MAX, INT64_MIN); + for (const Rect64& r : boundsList) + { + ++i; + if (!r.IsValid()) continue; // ignore invalid paths + else if (r.bottom > botPt.y || (r.bottom == botPt.y && r.left < botPt.x)) + { + botPt = Point64(r.left, r.bottom); + result = static_cast(i); + } + } + return result; } PointD GetUnitNormal(const Point64& pt1, const Point64& pt2) @@ -125,6 +146,34 @@ inline void NegatePath(PathD& path) } } + +//------------------------------------------------------------------------------ +// ClipperOffset::Group methods +//------------------------------------------------------------------------------ + +ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType _end_type): + paths_in(_paths), join_type(_join_type), end_type(_end_type) +{ + bool is_joined = + (end_type == EndType::Polygon) || + (end_type == EndType::Joined); + for (Path64& p: paths_in) + StripDuplicates(p, is_joined); + + GetMultiBounds(paths_in, bounds_list, end_type); + if (end_type == EndType::Polygon) + { + lowest_path_idx = GetLowestClosedPathIdx(bounds_list); + is_reversed = (lowest_path_idx >= 0) && Area(paths_in[lowest_path_idx]) < 0; + } + else + { + lowest_path_idx = -1; + is_reversed = false; + } +} + + //------------------------------------------------------------------------------ // ClipperOffset methods //------------------------------------------------------------------------------ @@ -200,7 +249,7 @@ PointD IntersectPoint(const PointD& pt1a, const PointD& pt1b, } } -void ClipperOffset::DoBevel(Group& group, const Path64& path, size_t j, size_t k) +void ClipperOffset::DoBevel(const Path64& path, size_t j, size_t k) { PointD pt1, pt2; if (j == k) @@ -214,11 +263,11 @@ void ClipperOffset::DoBevel(Group& group, const Path64& path, size_t j, size_t k pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y); pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y); } - group.path.push_back(Point64(pt1)); - group.path.push_back(Point64(pt2)); + path_out.push_back(Point64(pt1)); + path_out.push_back(Point64(pt2)); } -void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t k) +void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k) { PointD vec; if (j == k) @@ -246,8 +295,8 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t pt.z = ptQ.z; #endif //get the second intersect point through reflecion - group.path.push_back(Point64(ReflectPoint(pt, ptQ))); - group.path.push_back(Point64(pt)); + path_out.push_back(Point64(ReflectPoint(pt, ptQ))); + path_out.push_back(Point64(pt)); } else { @@ -256,28 +305,28 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t #ifdef USINGZ pt.z = ptQ.z; #endif - group.path.push_back(Point64(pt)); + path_out.push_back(Point64(pt)); //get the second intersect point through reflecion - group.path.push_back(Point64(ReflectPoint(pt, ptQ))); + path_out.push_back(Point64(ReflectPoint(pt, ptQ))); } } -void ClipperOffset::DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a) +void ClipperOffset::DoMiter(const Path64& path, size_t j, size_t k, double cos_a) { double q = group_delta_ / (cos_a + 1); #ifdef USINGZ - group.path.push_back(Point64( + path_out.push_back(Point64( path[j].x + (norms[k].x + norms[j].x) * q, path[j].y + (norms[k].y + norms[j].y) * q, path[j].z)); #else - group.path.push_back(Point64( + path_out.push_back(Point64( path[j].x + (norms[k].x + norms[j].x) * q, path[j].y + (norms[k].y + norms[j].y) * q)); #endif } -void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle) +void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle) { if (deltaCallback64_) { // when deltaCallback64_ is assigned, group_delta_ won't be constant, @@ -298,9 +347,9 @@ void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k if (j == k) offsetVec.Negate(); #ifdef USINGZ - group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); + path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); #else - group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); + path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); #endif int steps = static_cast(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456 for (int i = 1; i < steps; ++i) // ie 1 less than steps @@ -308,15 +357,15 @@ void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y, offsetVec.x * step_sin_ + offsetVec.y * step_cos_); #ifdef USINGZ - group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); + path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); #else - group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); + path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); #endif } - group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); + path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_)); } -void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) +void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size_t k) { // Let A = change in angle where edges join // A == 0: ie no change in angle (flat join) @@ -337,61 +386,51 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) } if (std::fabs(group_delta_) <= floating_point_tolerance) { - group.path.push_back(path[j]); + path_out.push_back(path[j]); return; } if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) // test for concavity first (#593) { // is concave - group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_)); + path_out.push_back(GetPerpendic(path[j], norms[k], group_delta_)); // this extra point is the only (simple) way to ensure that // path reversals are fully cleaned with the trailing clipper - group.path.push_back(path[j]); // (#405) - group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); + path_out.push_back(path[j]); // (#405) + path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_)); } else if (cos_a > 0.999) // almost straight - less than 2.5 degree (#424, #526) { - DoMiter(group, path, j, k, cos_a); + DoMiter(path, j, k, cos_a); } else if (join_type_ == JoinType::Miter) { // miter unless the angle is so acute the miter would exceeds ML - if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a); - else DoSquare(group, path, j, k); + if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a); + else DoSquare(path, j, k); } else if (cos_a > 0.99 || join_type_ == JoinType::Bevel) // ie > 2.5 deg (see above) but less than ~8 deg ( acos(0.99) ) - DoBevel(group, path, j, k); + DoBevel(path, j, k); else if (join_type_ == JoinType::Round) - DoRound(group, path, j, k, std::atan2(sin_a, cos_a)); + DoRound(path, j, k, std::atan2(sin_a, cos_a)); else - DoSquare(group, path, j, k); + DoSquare(path, j, k); } -void ClipperOffset::OffsetPolygon(Group& group, Path64& path) +void ClipperOffset::OffsetPolygon(Group& group, const Path64& path) { - // when the path is contracting, make sure - // there is sufficient space to do so. //#593 - // nb: this will have a small impact on performance - double a = Area(path); - // contracting when orientation is opposite offset direction - if ((a < 0) != (group_delta_ < 0)) - { - Rect64 rec = GetBounds(path); - double offsetMinDim = std::fabs(group_delta_) * 2; - if (offsetMinDim > rec.Width() || offsetMinDim > rec.Height()) return; - } - + path_out.clear(); for (Path64::size_type j = 0, k = path.size() -1; j < path.size(); k = j, ++j) OffsetPoint(group, path, j, k); - group.paths_out.push_back(group.path); + solution.push_back(path_out); } -void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path) +void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path) { OffsetPolygon(group, path); - std::reverse(path.begin(), path.end()); + Path64 reverse_path(path); + std::reverse(reverse_path.begin(), reverse_path.end()); //rebuild normals // BuildNormals(path); std::reverse(norms.begin(), norms.end()); @@ -399,29 +438,28 @@ void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path) norms.erase(norms.begin()); NegatePath(norms); - group.path.clear(); - OffsetPolygon(group, path); + OffsetPolygon(group, reverse_path); } -void ClipperOffset::OffsetOpenPath(Group& group, Path64& path) +void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path) { // do the line start cap if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0); if (std::fabs(group_delta_) <= floating_point_tolerance) - group.path.push_back(path[0]); + path_out.push_back(path[0]); else { switch (end_type_) { case EndType::Butt: - DoBevel(group, path, 0, 0); + DoBevel(path, 0, 0); break; case EndType::Round: - DoRound(group, path, 0, 0, PI); + DoRound(path, 0, 0, PI); break; default: - DoSquare(group, path, 0, 0); + DoSquare(path, 0, 0); break; } } @@ -441,54 +479,41 @@ void ClipperOffset::OffsetOpenPath(Group& group, Path64& path) group_delta_ = deltaCallback64_(path, norms, highI, highI); if (std::fabs(group_delta_) <= floating_point_tolerance) - group.path.push_back(path[highI]); + path_out.push_back(path[highI]); else { switch (end_type_) { case EndType::Butt: - DoBevel(group, path, highI, highI); + DoBevel(path, highI, highI); break; case EndType::Round: - DoRound(group, path, highI, highI, PI); + DoRound(path, highI, highI, PI); break; default: - DoSquare(group, path, highI, highI); + DoSquare(path, highI, highI); break; } } for (size_t j = highI, k = 0; j > 0; k = j, --j) OffsetPoint(group, path, j, k); - group.paths_out.push_back(group.path); + solution.push_back(path_out); } void ClipperOffset::DoGroupOffset(Group& group) { - Rect64 r; - int idx = -1; - //the lowermost polygon must be an outer polygon. So we can use that as the - //designated orientation for outer polygons (needed for tidy-up clipping) - GetBoundsAndLowestPolyIdx(group.paths_in, r, idx); - if (idx < 0) return; - if (group.end_type == EndType::Polygon) { - double area = Area(group.paths_in[idx]); + if (group.lowest_path_idx < 0) return; //if (area == 0) return; // probably unhelpful (#430) - group.is_reversed = (area < 0); - if (group.is_reversed) group_delta_ = -delta_; - else group_delta_ = delta_; + group_delta_ = (group.is_reversed) ? -delta_ : delta_; } else - { - group.is_reversed = false; group_delta_ = std::abs(delta_) * 0.5; - } double abs_delta = std::fabs(group_delta_); - // do range checking - if (!IsSafeOffset(r, abs_delta)) + if (!ValidateBounds(group.bounds_list, abs_delta)) { DoError(range_error_i); error_code_ |= range_error_i; @@ -498,8 +523,7 @@ void ClipperOffset::DoGroupOffset(Group& group) join_type_ = group.join_type; end_type_ = group.end_type; - if (!deltaCallback64_ && - (group.join_type == JoinType::Round || group.end_type == EndType::Round)) + if (group.join_type == JoinType::Round || group.end_type == EndType::Round) { //calculate a sensible number of steps (for 360 deg for the given offset) // arcTol - when arc_tolerance_ is undefined (0), the amount of @@ -517,62 +541,77 @@ void ClipperOffset::DoGroupOffset(Group& group) steps_per_rad_ = steps_per_360 / (2 * PI); } - bool is_joined = - (end_type_ == EndType::Polygon) || - (end_type_ == EndType::Joined); - Paths64::iterator path_iter; - for(path_iter = group.paths_in.begin(); path_iter != group.paths_in.end(); ++path_iter) + size_t i = 0; + for (const Path64& path_in : group.paths_in) { - Path64 &path = *path_iter; - StripDuplicates(path, is_joined); - Path64::size_type cnt = path.size(); - if (cnt == 0 || ((cnt < 3) && group.end_type == EndType::Polygon)) - continue; + const Rect64& path_rect = group.bounds_list[i++]; + if (!path_rect.IsValid()) continue; + Path64::size_type pathLen = path_in.size(); + path_out.clear(); - group.path.clear(); - if (cnt == 1) // single point - only valid with open paths + if (pathLen == 1) // single point - only valid with open paths { if (group_delta_ < 1) continue; + const Point64& pt = path_in[0]; //single vertex so build a circle or square ... if (group.join_type == JoinType::Round) { double radius = abs_delta; int steps = static_cast(std::ceil(steps_per_rad_ * 2 * PI)); //#617 - group.path = Ellipse(path[0], radius, radius, steps); + path_out = Ellipse(pt, radius, radius, steps); #ifdef USINGZ - for (auto& p : group.path) p.z = path[0].z; + for (auto& p : path_out) p.z = pt.z; #endif } else { int d = (int)std::ceil(abs_delta); - r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d); - group.path = r.AsPath(); + Rect64 r = Rect64(pt.x - d, pt.y - d, pt.x + d, pt.y + d); + path_out = r.AsPath(); #ifdef USINGZ - for (auto& p : group.path) p.z = path[0].z; + for (auto& p : path_out) p.z = pt.z; #endif } - group.paths_out.push_back(group.path); - } - else - { - if ((cnt == 2) && (group.end_type == EndType::Joined)) - { - if (group.join_type == JoinType::Round) - end_type_ = EndType::Round; - else - end_type_ = EndType::Square; - } + solution.push_back(path_out); + continue; + } // end of offsetting a single (open path) point + + // when shrinking, then make sure the path can shrink that far (#593) + if (group_delta_ < 0 && + std::min(path_rect.Width(), path_rect.Height()) < -group_delta_ * 2) + continue; + + if ((pathLen == 2) && (group.end_type == EndType::Joined)) + end_type_ = (group.join_type == JoinType::Round) ? + EndType::Round : + EndType::Square; + + BuildNormals(path_in); + if (end_type_ == EndType::Polygon) OffsetPolygon(group, path_in); + else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, path_in); + else OffsetOpenPath(group, path_in); + } +} + + +size_t ClipperOffset::CalcSolutionCapacity() +{ + size_t result = 0; + for (const Group& g : groups_) + result += (g.end_type == EndType::Joined) ? g.paths_in.size() * 2 : g.paths_in.size(); + return result; +} - BuildNormals(path); - if (end_type_ == EndType::Polygon) OffsetPolygon(group, path); - else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, path); - else OffsetOpenPath(group, path); +bool ClipperOffset::CheckReverseOrientation() +{ + bool is_reversed_orientation = false; + for (const Group& g : groups_) + if (g.end_type == EndType::Polygon) + { + is_reversed_orientation = g.is_reversed; + break; } - } - solution.reserve(solution.size() + group.paths_out.size()); - copy(group.paths_out.begin(), group.paths_out.end(), back_inserter(solution)); - group.paths_out.clear(); + return is_reversed_orientation; } void ClipperOffset::ExecuteInternal(double delta) @@ -580,29 +619,29 @@ void ClipperOffset::ExecuteInternal(double delta) error_code_ = 0; solution.clear(); if (groups_.size() == 0) return; + solution.reserve(CalcSolutionCapacity()); - if (std::abs(delta) < 0.5) + if (std::abs(delta) < 0.5) // ie: offset is insignificant { + int64_t sol_size = 0; + for (const Group& group : groups_) sol_size += group.paths_in.size(); + solution.reserve(sol_size); for (const Group& group : groups_) - { - solution.reserve(solution.size() + group.paths_in.size()); copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(solution)); - } - } - else - { - temp_lim_ = (miter_limit_ <= 1) ? - 2.0 : - 2.0 / (miter_limit_ * miter_limit_); + return; + } - delta_ = delta; - std::vector::iterator git; - for (git = groups_.begin(); git != groups_.end(); ++git) - { - DoGroupOffset(*git); - if (!error_code_) continue; // all OK - solution.clear(); - } + temp_lim_ = (miter_limit_ <= 1) ? + 2.0 : + 2.0 / (miter_limit_ * miter_limit_); + + delta_ = delta; + std::vector::iterator git; + for (git = groups_.begin(); git != groups_.end(); ++git) + { + DoGroupOffset(*git); + if (!error_code_) continue; // all OK + solution.clear(); } } @@ -613,19 +652,17 @@ void ClipperOffset::Execute(double delta, Paths64& paths) ExecuteInternal(delta); if (!solution.size()) return; - paths = solution; + bool paths_reversed = CheckReverseOrientation(); //clean up self-intersections ... Clipper64 c; c.PreserveCollinear = false; //the solution should retain the orientation of the input - c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed; + c.ReverseSolution = (reverse_solution_ != paths_reversed); #ifdef USINGZ - if (zCallback64_) { - c.SetZCallback(zCallback64_); - } + if (zCallback64_) { c.SetZCallback(zCallback64_); } #endif c.AddSubject(solution); - if (groups_[0].is_reversed) + if (paths_reversed) c.Execute(ClipType::Union, FillRule::Negative, paths); else c.Execute(ClipType::Union, FillRule::Positive, paths); @@ -639,18 +676,21 @@ void ClipperOffset::Execute(double delta, PolyTree64& polytree) ExecuteInternal(delta); if (!solution.size()) return; + bool paths_reversed = CheckReverseOrientation(); //clean up self-intersections ... Clipper64 c; c.PreserveCollinear = false; //the solution should retain the orientation of the input - c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed; + c.ReverseSolution = reverse_solution_ != paths_reversed; #ifdef USINGZ if (zCallback64_) { c.SetZCallback(zCallback64_); } #endif c.AddSubject(solution); - if (groups_[0].is_reversed) + + + if (paths_reversed) c.Execute(ClipType::Union, FillRule::Negative, polytree); else c.Execute(ClipType::Union, FillRule::Positive, polytree); diff --git a/CPP/Tests/TestOffsetOrientation.cpp b/CPP/Tests/TestOffsetOrientation.cpp index 03c1f11a..8ebfc533 100644 --- a/CPP/Tests/TestOffsetOrientation.cpp +++ b/CPP/Tests/TestOffsetOrientation.cpp @@ -1,21 +1,32 @@ #include #include "clipper2/clipper.offset.h" +#include "ClipFileLoad.h" -TEST(Clipper2Tests, TestOffsettingOrientation) { - Clipper2Lib::ClipperOffset co; +TEST(Clipper2Tests, TestOffsettingOrientation1) { + const Clipper2Lib::Paths64 subject = { Clipper2Lib::MakePath({ 0,0, 0,5, 5,5, 5,0 }) }; + Clipper2Lib::Paths64 solution = Clipper2Lib::InflatePaths(subject, 1, + Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); + ASSERT_EQ(solution.size(), 1); + //when offsetting, output orientation should match input + EXPECT_TRUE(Clipper2Lib::IsPositive(subject[0]) == Clipper2Lib::IsPositive(solution[0])); +} - const Clipper2Lib::Path64 input = { - Clipper2Lib::Point64(0, 0), - Clipper2Lib::Point64(0, 5), - Clipper2Lib::Point64(5, 5), - Clipper2Lib::Point64(5, 0) - }; +TEST(Clipper2Tests, TestOffsettingOrientation2) { - co.AddPath(input, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); - Clipper2Lib::Paths64 outputs; - co.Execute(1, outputs); + const Clipper2Lib::Paths64 subject = { + Clipper2Lib::MakePath({20, 220, 280, 220, 280, 280, 20, 280}), + Clipper2Lib::MakePath({0, 200, 0, 300, 300, 300, 300, 200}) + }; + Clipper2Lib::ClipperOffset co; + co.ReverseSolution(true); // could also assign using a parameter in ClipperOffset's constructor + co.AddPaths(subject, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); + Clipper2Lib::Paths64 solution; + co.Execute(5, solution); + + ASSERT_EQ(solution.size(), 2); + // when offsetting, output orientation should match input EXCEPT when ReverseSolution == true + // HOWEVER, input path order MANY NOT MATCH output path order, for example when inner paths (holes) + // are defined before their container outer paths (as above). + EXPECT_TRUE(Clipper2Lib::IsPositive(subject[1]) != Clipper2Lib::IsPositive(solution[0])); +} - ASSERT_EQ(outputs.size(), 1); - //when offsetting, output orientation should match input - EXPECT_TRUE(Clipper2Lib::IsPositive(input) == Clipper2Lib::IsPositive(outputs[0])); -} \ No newline at end of file diff --git a/CPP/Tests/TestOffsets.cpp b/CPP/Tests/TestOffsets.cpp index 6fa5e9db..84776ad9 100644 --- a/CPP/Tests/TestOffsets.cpp +++ b/CPP/Tests/TestOffsets.cpp @@ -151,225 +151,226 @@ TEST(Clipper2Tests, TestOffsets4) // see #482 TEST(Clipper2Tests, TestOffsets5) // modified from #593 (tests offset clean up) { Paths64 subject = { - {{524,1483}, {524,2711}, {610,2744}, {693,2782}, {773,2825}, {852,2872}, - {927,2924}, {999,2980}, {1068,3040}, {1133,3103}, {1195,3171}, {1252,3242}, - {1305,3316}, {1354,3394}, {1398,3473}, {1437,3556}, {1472,3640}, {1502,3727}, - {1526,3815}, {1546,3904}, {1560,3994}, {1569,4085}, {1573,4176}, {1571,4267}, - {1564,4358}, {1552,4449}, {1535,4539}, {1512,4627}, {1485,4714}, {1452,4799}, - {1414,4883}, {1372,4964}, {1325,5042}, {1274,5117}, {1218,5190}, {1158,5259}, - {1094,5324}, {1027,5386}, {956,5443}, {882,5497}, {805,5546}, {725,5590}, - {643,5630}, {559,5665}, {524,5677}, {524,6906}, {610,6939}, {693,6977}, - {773,7019}, {852,7066}, {927,7118}, {999,7174}, {1068,7234}, {1133,7298}, - {1195,7365}, {1252,7436}, {1305,7511}, {1354,7588}, {1398,7668}, {1437,7750}, - {1472,7835}, {1502,7921}, {1526,8009}, {1546,8098}, {1560,8188}, {1569,8279}, - {1573,8370}, {1571,8462}, {1564,8553}, {1552,8643}, {1535,8733}, {1512,8821}, - {1485,8908}, {1452,8994}, {1414,9077}, {1372,9158}, {1325,9236}, {1274,9312}, - {1218,9384}, {1158,9453}, {1094,9518}, {1027,9580}, {956,9638}, {882,9691}, - {805,9740}, {725,9784}, {643,9824}, {559,9859}, {524,9872}, {524,11100}, - {610,11133}, {693,11171}, {773,11213}, {852,11261}, {927,11312}, {999,11368}, - {1068,11428}, {1133,11492}, {1195,11560}, {1252,11631}, {1305,11705}, {1354,11782}, - {1398,11862}, {1437,11945}, {1472,12029}, {1502,12115}, {1526,12203}, {1546,12293}, - {1560,12383}, {1569,12474}, {1573,12565}, {1571,12656}, {1564,12747}, {1552,12838}, - {1535,12927}, {1512,13016}, {1485,13103}, {1452,13188}, {1414,13271}, {1372,13352}, - {1325,13431}, {1274,13506}, {1218,13578}, {1158,13647}, {1094,13713}, {1027,13774}, - {956,13832}, {882,13885}, {805,13934}, {725,13979}, {643,14019}, {559,14053}, - {524,14066}, {524,15295}, {610,15327}, {693,15365}, {773,15408}, {852,15455}, - {927,15507}, {999,15563}, {1068,15623}, {1133,15687}, {1195,15754}, {1252,15825}, - {1305,15899}, {1354,15977}, {1398,16057}, {1437,16139}, {1472,16223}, {1502,16310}, - {1526,16398}, {1546,16487}, {1560,16577}, {1569,16668}, {1573,16759}, {1571,16850}, - {1564,16942}, {1552,17032}, {1535,17122}, {1512,17210}, {1485,17297}, {1452,17382}, - {1414,17466}, {1372,17547}, {1325,17625}, {1274,17700}, {1218,17773}, {1158,17842}, - {1094,17907}, {1027,17969}, {956,18026}, {882,18080}, {805,18129}, {725,18173}, - {643,18213}, {559,18248}, {524,18260}, {524,19489}, {610,19522}, {693,19560}, - {773,19602}, {852,19649}, {927,19701}, {999,19757}, {1068,19817}, {1133,19881}, - {1195,19948}, {1252,20019}, {1305,20094}, {1354,20171}, {1398,20251}, {1437,20333}, - {1472,20418}, {1502,20504}, {1526,20592}, {1546,20681}, {1560,20771}, {1569,20862}, - {1573,20954}, {1571,21045}, {1564,21136}, {1552,21226}, {1535,21316}, {1512,21404}, - {1485,21492}, {1452,21577}, {1414,21660}, {1372,21741}, {1325,21819}, {1274,21895}, - {1218,21967}, {1158,22036}, {1094,22101}, {1027,22163}, {956,22221}, {882,22274}, - {805,22323}, {725,22368}, {643,22407}, {559,22442}, {524,22455}, {524,23683}, - {610,23716}, {693,23754}, {773,23797}, {852,23844}, {927,23895}, {999,23951}, - {1068,24011}, {1133,24075}, {1195,24143}, {1252,24214}, {1305,24288}, {1354,24365}, - {1398,24445}, {1437,24528}, {1472,24612}, {1502,24698}, {1526,24786}, {1546,24876}, - {1560,24966}, {1569,25057}, {1573,25148}, {1571,25239}, {1564,25330}, {1552,25421}, - {1535,25510}, {1512,25599}, {1485,25686}, {1452,25771}, {1414,25854}, {1372,25935}, - {1325,26014}, {1274,26089}, {1218,26161}, {1158,26230}, {1094,26296}, {1027,26357}, - {956,26415}, {882,26468}, {805,26517}, {725,26562}, {643,26602}, {559,26636}, - {524,26649}, {524,27878}, {610,27910}, {693,27948}, {773,27991}, {852,28038}, - {927,28090}, {999,28146}, {1068,28206}, {1133,28270}, {1195,28337}, {1252,28408}, - {1305,28482}, {1354,28560}, {1398,28640}, {1437,28722}, {1472,28806}, {1502,28893}, - {1526,28981}, {1546,29070}, {1560,29160}, {1569,29251}, {1573,29342}, {1571,29434}, - {1564,29525}, {1552,29615}, {1535,29705}, {1512,29793}, {1485,29880}, {1452,29965}, - {1414,30049}, {1372,30130}, {1325,30208}, {1274,30283}, {1218,30356}, {1158,30425}, - {1094,30490}, {1027,30552}, {956,30609}, {882,30663}, {805,30712}, {725,30756}, - {643,30796}, {559,30831}, {524,30843}, {524,32072}, {609,32105}, {692,32142}, - {773,32185}, {851,32232}, {926,32283}, {998,32339}, {1066,32398}, {1131,32462}, - {1193,32529}, {1250,32600}, {1303,32674}, {1352,32751}, {1396,32830}, {1436,32912}, - {1470,32996}, {1483,33031}, {3131,33031}, {3164,32945}, {3202,32862}, {3244,32781}, - {3291,32703}, {3343,32628}, {3399,32556}, {3459,32487}, {3523,32422}, {3591,32360}, - {3662,32303}, {3736,32250}, {3813,32201}, {3893,32157}, {3975,32117}, {4060,32083}, - {4146,32053}, {4234,32028}, {4323,32009}, {4413,31995}, {4504,31986}, {4596,31982}, - {4687,31984}, {4778,31991}, {4868,32003}, {4958,32020}, {5047,32043}, {5134,32070}, - {5219,32103}, {5302,32141}, {5383,32183}, {5461,32230}, {5537,32281}, {5609,32337}, - {5678,32397}, {5744,32460}, {5805,32528}, {5863,32599}, {5916,32673}, {5965,32750}, - {6010,32830}, {6049,32912}, {6084,32996}, {6097,33031}, {7745,33031}, {7778,32945}, - {7815,32862}, {7858,32781}, {7905,32703}, {7957,32628}, {8013,32556}, {8073,32487}, - {8137,32422}, {8204,32360}, {8275,32303}, {8350,32250}, {8427,32201}, {8507,32157}, - {8589,32117}, {8674,32083}, {8760,32053}, {8848,32028}, {8937,32009}, {9027,31995}, - {9118,31986}, {9209,31982}, {9301,31984}, {9392,31991}, {9482,32003}, {9572,32020}, - {9660,32043}, {9747,32070}, {9833,32103}, {9916,32141}, {9997,32183}, {10075,32230}, - {10151,32281}, {10223,32337}, {10292,32397}, {10357,32460}, {10419,32528}, {10477,32599}, - {10530,32673}, {10579,32750}, {10623,32830}, {10663,32912}, {10698,32996}, {10711,33031}, - {12358,33031}, {12391,32945}, {12429,32862}, {12472,32781}, {12519,32703}, {12571,32628}, - {12627,32556}, {12687,32487}, {12750,32422}, {12818,32360}, {12889,32303}, {12963,32250}, - {13041,32201}, {13120,32157}, {13203,32117}, {13287,32083}, {13374,32053}, {13462,32028}, - {13551,32009}, {13641,31995}, {13732,31986}, {13823,31982}, {13914,31984}, {14005,31991}, - {14096,32003}, {14186,32020}, {14274,32043}, {14361,32070}, {14446,32103}, {14530,32141}, - {14611,32183}, {14689,32230}, {14764,32281}, {14837,32337}, {14906,32397}, {14971,32460}, - {15033,32528}, {15090,32599}, {15144,32673}, {15193,32750}, {15237,32830}, {15277,32912}, - {15312,32996}, {15324,33031}, {16972,33031}, {17005,32945}, {17043,32862}, {17086,32781}, - {17133,32703}, {17184,32628}, {17240,32556}, {17300,32487}, {17364,32422}, {17432,32360}, - {17503,32303}, {17577,32250}, {17654,32201}, {17734,32157}, {17817,32117}, {17901,32083}, - {17987,32053}, {18075,32028}, {18165,32009}, {18255,31995}, {18346,31986}, {18437,31982}, - {18528,31984}, {18619,31991}, {18710,32003}, {18799,32020}, {18888,32043}, {18975,32070}, - {19060,32103}, {19143,32141}, {19224,32183}, {19303,32230}, {19378,32281}, {19450,32337}, - {19519,32397}, {19585,32460}, {19646,32528}, {19704,32599}, {19757,32673}, {19806,32750}, - {19851,32830}, {19891,32912}, {19926,32996}, {19938,33031}, {21586,33031}, {21619,32945}, - {21657,32862}, {21699,32781}, {21747,32703}, {21798,32628}, {21854,32556}, {21914,32487}, - {21978,32422}, {22046,32360}, {22117,32303}, {22191,32250}, {22268,32201}, {22348,32157}, - {22430,32117}, {22515,32083}, {22601,32053}, {22689,32028}, {22778,32009}, {22869,31995}, - {22959,31986}, {23051,31982}, {23142,31984}, {23233,31991}, {23324,32003}, {23413,32020}, - {23502,32043}, {23589,32070}, {23674,32103}, {23757,32141}, {23838,32183}, {23916,32230}, - {23992,32281}, {24064,32337}, {24133,32397}, {24199,32460}, {24260,32528}, {24318,32599}, - {24371,32673}, {24420,32750}, {24465,32830}, {24504,32912}, {24539,32996}, {24552,33031}, - {26200,33031}, {26233,32945}, {26271,32862}, {26313,32781}, {26360,32703}, {26412,32628}, - {26468,32556}, {26528,32487}, {26592,32422}, {26659,32360}, {26730,32303}, {26805,32250}, - {26882,32201}, {26962,32157}, {27044,32117}, {27129,32083}, {27215,32053}, {27303,32028}, - {27392,32009}, {27482,31995}, {27573,31986}, {27664,31982}, {27756,31984}, {27847,31991}, - {27937,32003}, {28027,32020}, {28115,32043}, {28202,32070}, {28288,32103}, {28371,32141}, - {28452,32183}, {28530,32230}, {28606,32281}, {28678,32337}, {28747,32397}, {28812,32460}, - {28874,32528}, {28932,32599}, {28985,32673}, {29034,32750}, {29078,32830}, {29118,32912}, - {29153,32996}, {29166,33031}, {30814,33031}, {30847,32945}, {30884,32862}, {30927,32781}, - {30974,32703}, {31026,32628}, {31082,32556}, {31142,32487}, {31206,32422}, {31273,32360}, - {31344,32303}, {31418,32250}, {31496,32201}, {31576,32157}, {31658,32117}, {31742,32083}, - {31829,32053}, {31917,32028}, {32006,32009}, {32096,31995}, {32187,31986}, {32278,31982}, - {32370,31984}, {32461,31991}, {32551,32003}, {32641,32020}, {32729,32043}, {32816,32070}, - {32902,32103}, {32985,32141}, {33066,32183}, {33144,32230}, {33219,32281}, {33292,32337}, - {33361,32397}, {33426,32460}, {33488,32528}, {33545,32599}, {33599,32673}, {33648,32750}, - {33692,32830}, {33732,32912}, {33767,32996}, {33779,33031}, {35427,33031}, {35460,32946}, - {35498,32863}, {35540,32782}, {35587,32704}, {35639,32629}, {35694,32557}, {35754,32489}, - {35818,32423}, {35885,32362}, {35956,32305}, {36029,32252}, {36106,32203}, {36186,32159}, - {36268,32119}, {36352,32084}, {36386,32072}, {36386,30843}, {36301,30810}, {36218,30773}, - {36137,30730}, {36059,30683}, {35983,30631}, {35911,30575}, {35842,30515}, {35777,30451}, - {35716,30384}, {35658,30313}, {35605,30239}, {35557,30161}, {35512,30081}, {35473,29999}, - {35438,29915}, {35409,29828}, {35384,29740}, {35364,29651}, {35350,29561}, {35341,29470}, - {35338,29379}, {35339,29287}, {35346,29196}, {35358,29106}, {35376,29016}, {35398,28928}, - {35426,28841}, {35458,28755}, {35496,28672}, {35538,28591}, {35585,28513}, {35637,28438}, - {35692,28365}, {35752,28296}, {35816,28231}, {35883,28169}, {35954,28112}, {36028,28058}, - {36105,28009}, {36185,27965}, {36267,27925}, {36352,27890}, {36386,27878}, {36386,26649}, - {36301,26616}, {36218,26578}, {36137,26536}, {36059,26489}, {35983,26437}, {35911,26381}, - {35842,26321}, {35777,26257}, {35716,26189}, {35658,26118}, {35605,26044}, {35557,25967}, - {35512,25887}, {35473,25805}, {35438,25720}, {35409,25634}, {35384,25546}, {35364,25457}, - {35350,25366}, {35341,25276}, {35338,25184}, {35339,25093}, {35346,25002}, {35358,24912}, - {35376,24822}, {35398,24733}, {35426,24646}, {35458,24561}, {35496,24478}, {35538,24397}, - {35585,24319}, {35637,24243}, {35692,24171}, {35752,24102}, {35816,24036}, {35883,23975}, - {35954,23917}, {36028,23864}, {36105,23815}, {36185,23770}, {36267,23731}, {36352,23696}, - {36386,23683}, {36386,22455}, {36301,22422}, {36218,22384}, {36137,22341}, {36059,22294}, - {35983,22243}, {35911,22187}, {35842,22127}, {35777,22063}, {35716,21995}, {35658,21924}, - {35605,21850}, {35557,21773}, {35512,21693}, {35473,21610}, {35438,21526}, {35409,21440}, - {35384,21352}, {35364,21262}, {35350,21172}, {35341,21081}, {35338,20990}, {35339,20899}, - {35346,20808}, {35358,20717}, {35376,20628}, {35398,20539}, {35426,20452}, {35458,20367}, - {35496,20284}, {35538,20203}, {35585,20124}, {35637,20049}, {35692,19976}, {35752,19907}, - {35816,19842}, {35883,19780}, {35954,19723}, {36028,19669}, {36105,19620}, {36185,19576}, - {36267,19536}, {36352,19501}, {36386,19489}, {36386,18260}, {36301,18227}, {36218,18190}, - {36137,18147}, {36059,18100}, {35983,18048}, {35911,17992}, {35842,17932}, {35777,17868}, - {35716,17801}, {35658,17730}, {35605,17655}, {35557,17578}, {35512,17498}, {35473,17416}, - {35438,17332}, {35409,17245}, {35384,17157}, {35364,17068}, {35350,16978}, {35341,16887}, - {35338,16796}, {35339,16704}, {35346,16613}, {35358,16523}, {35376,16433}, {35398,16345}, - {35426,16258}, {35458,16172}, {35496,16089}, {35538,16008}, {35585,15930}, {35637,15854}, - {35692,15782}, {35752,15713}, {35816,15648}, {35883,15586}, {35954,15529}, {36028,15475}, - {36105,15426}, {36185,15382}, {36267,15342}, {36352,15307}, {36386,15295}, {36386,14066}, - {36301,14033}, {36218,13995}, {36137,13953}, {36059,13906}, {35983,13854}, {35911,13798}, - {35842,13738}, {35777,13674}, {35716,13606}, {35658,13535}, {35605,13461}, {35557,13384}, - {35512,13304}, {35473,13222}, {35438,13137}, {35409,13051}, {35384,12963}, {35364,12874}, - {35350,12783}, {35341,12693}, {35338,12601}, {35339,12510}, {35346,12419}, {35358,12328}, - {35376,12239}, {35398,12150}, {35426,12063}, {35458,11978}, {35496,11895}, {35538,11814}, - {35585,11736}, {35637,11660}, {35692,11588}, {35752,11519}, {35816,11453}, {35883,11392}, - {35954,11334}, {36028,11281}, {36105,11232}, {36185,11187}, {36267,11148}, {36352,11113}, - {36386,11100}, {36386,9872}, {36301,9839}, {36218,9801}, {36137,9758}, {36059,9711}, - {35983,9660}, {35911,9604}, {35842,9544}, {35777,9480}, {35716,9412}, {35658,9341}, - {35605,9267}, {35557,9190}, {35512,9110}, {35473,9027}, {35438,8943}, {35409,8856}, - {35384,8769}, {35364,8679}, {35350,8589}, {35341,8498}, {35338,8407}, {35339,8316}, - {35346,8225}, {35358,8134}, {35376,8045}, {35398,7956}, {35426,7869}, {35458,7784}, - {35496,7700}, {35538,7620}, {35585,7541}, {35637,7466}, {35692,7393}, {35752,7324}, - {35816,7259}, {35883,7197}, {35954,7140}, {36028,7086}, {36105,7037}, {36185,6993}, - {36267,6953}, {36352,6918}, {36386,6906}, {36386,5677}, {36301,5644}, {36218,5607}, - {36137,5564}, {36059,5517}, {35983,5465}, {35911,5409}, {35842,5349}, {35777,5285}, - {35716,5218}, {35658,5147}, {35605,5072}, {35557,4995}, {35512,4915}, {35473,4833}, - {35438,4748}, {35409,4662}, {35384,4574}, {35364,4485}, {35350,4395}, {35341,4304}, - {35338,4213}, {35339,4121}, {35346,4030}, {35358,3940}, {35376,3850}, {35398,3762}, - {35426,3675}, {35458,3589}, {35496,3506}, {35538,3425}, {35585,3347}, {35637,3271}, - {35692,3199}, {35752,3130}, {35816,3065}, {35883,3003}, {35954,2945}, {36028,2892}, - {36105,2843}, {36185,2799}, {36267,2759}, {36352,2724}, {36386,2711}, {36386,1483}, - {36301,1450}, {36218,1413}, {36138,1370}, {36060,1323}, {35985,1272}, {35913,1216}, - {35844,1156}, {35779,1093}, {35718,1026}, {35660,955}, {35607,881}, {35558,804}, - {35514,725}, {35475,643}, {35440,559}, {35427,524}, {33779,524}, {33747,610}, - {33709,693}, {33666,773}, {33619,852}, {33567,927}, {33511,999}, {33451,1068}, - {33387,1133}, {33320,1195}, {33249,1252}, {33175,1305}, {33097,1354}, {33017,1398}, - {32935,1437}, {32851,1472}, {32764,1502}, {32676,1526}, {32587,1546}, {32497,1560}, - {32406,1569}, {32315,1573}, {32223,1571}, {32132,1564}, {32042,1552}, {31952,1535}, - {31864,1512}, {31777,1485}, {31691,1452}, {31608,1414}, {31527,1372}, {31449,1325}, - {31374,1274}, {31301,1218}, {31232,1158}, {31167,1094}, {31105,1027}, {31048,956}, - {30994,882}, {30945,805}, {30901,725}, {30861,643}, {30826,559}, {30814,524}, - {29166,524}, {29133,610}, {29095,693}, {29052,773}, {29005,852}, {28954,927}, - {28898,999}, {28838,1068}, {28774,1133}, {28706,1195}, {28635,1252}, {28561,1305}, - {28484,1354}, {28404,1398}, {28321,1437}, {28237,1472}, {28150,1502}, {28063,1526}, - {27973,1546}, {27883,1560}, {27792,1569}, {27701,1573}, {27610,1571}, {27519,1564}, - {27428,1552}, {27338,1535}, {27250,1512}, {27163,1485}, {27078,1452}, {26994,1414}, - {26914,1372}, {26835,1325}, {26760,1274}, {26687,1218}, {26618,1158}, {26553,1094}, - {26491,1027}, {26434,956}, {26380,882}, {26331,805}, {26287,725}, {26247,643}, - {26212,559}, {26200,524}, {24552,524}, {24519,610}, {24481,693}, {24439,773}, - {24391,852}, {24340,927}, {24284,999}, {24224,1068}, {24160,1133}, {24092,1195}, - {24021,1252}, {23947,1305}, {23870,1354}, {23790,1398}, {23708,1437}, {23623,1472}, - {23537,1502}, {23449,1526}, {23360,1546}, {23269,1560}, {23178,1569}, {23087,1573}, - {22996,1571}, {22905,1564}, {22814,1552}, {22725,1535}, {22636,1512}, {22549,1485}, - {22464,1452}, {22381,1414}, {22300,1372}, {22221,1325}, {22146,1274}, {22074,1218}, - {22005,1158}, {21939,1094}, {21878,1027}, {21820,956}, {21767,882}, {21718,805}, - {21673,725}, {21633,643}, {21599,559}, {21586,524}, {19938,524}, {19905,610}, - {19867,693}, {19825,773}, {19778,852}, {19726,927}, {19670,999}, {19610,1068}, - {19546,1133}, {19478,1195}, {19407,1252}, {19333,1305}, {19256,1354}, {19176,1398}, - {19094,1437}, {19009,1472}, {18923,1502}, {18835,1526}, {18746,1546}, {18656,1560}, - {18565,1569}, {18473,1573}, {18382,1571}, {18291,1564}, {18201,1552}, {18111,1535}, - {18022,1512}, {17935,1485}, {17850,1452}, {17767,1414}, {17686,1372}, {17608,1325}, - {17532,1274}, {17460,1218}, {17391,1158}, {17325,1094}, {17264,1027}, {17206,956}, - {17153,882}, {17104,805}, {17059,725}, {17020,643}, {16985,559}, {16972,524}, - {15324,524}, {15291,610}, {15254,693}, {15211,773}, {15164,852}, {15112,927}, - {15056,999}, {14996,1068}, {14932,1133}, {14865,1195}, {14794,1252}, {14719,1305}, - {14642,1354}, {14562,1398}, {14480,1437}, {14395,1472}, {14309,1502}, {14221,1526}, - {14132,1546}, {14042,1560}, {13951,1569}, {13860,1573}, {13768,1571}, {13677,1564}, - {13587,1552}, {13497,1535}, {13409,1512}, {13322,1485}, {13236,1452}, {13153,1414}, - {13072,1372}, {12994,1325}, {12918,1274}, {12846,1218}, {12777,1158}, {12712,1094}, - {12650,1027}, {12592,956}, {12539,882}, {12490,805}, {12446,725}, {12406,643}, - {12371,559}, {12358,524}, {10711,524}, {10678,610}, {10640,693}, {10597,773}, - {10550,852}, {10498,927}, {10442,999}, {10382,1068}, {10319,1133}, {10251,1195}, - {10180,1252}, {10106,1305}, {10028,1354}, {9949,1398}, {9866,1437}, {9782,1472}, - {9695,1502}, {9607,1526}, {9518,1546}, {9428,1560}, {9337,1569}, {9246,1573}, - {9155,1571}, {9064,1564}, {8973,1552}, {8883,1535}, {8795,1512}, {8708,1485}, - {8623,1452}, {8539,1414}, {8458,1372}, {8380,1325}, {8305,1274}, {8232,1218}, - {8163,1158}, {8098,1094}, {8036,1027}, {7979,956}, {7925,882}, {7876,805}, - {7832,725}, {7792,643}, {7757,559}, {7745,524}, {6097,524}, {6064,610}, - {6026,693}, {5983,773}, {5936,852}, {5885,927}, {5829,999}, {5769,1068}, - {5705,1133}, {5637,1195}, {5566,1252}, {5492,1305}, {5415,1354}, {5335,1398}, - {5252,1437}, {5168,1472}, {5082,1502}, {4994,1526}, {4904,1546}, {4814,1560}, - {4723,1569}, {4632,1573}, {4541,1571}, {4450,1564}, {4359,1552}, {4270,1535}, - {4181,1512}, {4094,1485}, {4009,1452}, {3926,1414}, {3845,1372}, {3766,1325}, - {3691,1274}, {3619,1218}, {3550,1158}, {3484,1094}, {3423,1027}, {3365,956}, - {3312,882}, {3263,805}, {3218,725}, {3178,643}, {3143,559}, {3131,524}, - {1483,524}, {1450,609}, {1413,692}, {1370,773}, {1323,851}, {1272,926}, - {1216,998}, {1156,1066}, {1093,1131}, {1026,1193}, {955,1250}, {881,1303}, - {804,1352}, {725,1396}, {643,1436}, {559,1470}}, - - {{-47877,-47877}, {84788,-47877}, {84788,81432}, {-47877,81432}} + MakePath({ + 524,1483, 524,2711, 610,2744, 693,2782, 773,2825, 852,2872, + 927,2924, 999,2980, 1068,3040, 1133,3103, 1195,3171, 1252,3242, + 1305,3316, 1354,3394, 1398,3473, 1437,3556, 1472,3640, 1502,3727, + 1526,3815, 1546,3904, 1560,3994, 1569,4085, 1573,4176, 1571,4267, + 1564,4358, 1552,4449, 1535,4539, 1512,4627, 1485,4714, 1452,4799, + 1414,4883, 1372,4964, 1325,5042, 1274,5117, 1218,5190, 1158,5259, + 1094,5324, 1027,5386, 956,5443, 882,5497, 805,5546, 725,5590, + 643,5630, 559,5665, 524,5677, 524,6906, 610,6939, 693,6977, + 773,7019, 852,7066, 927,7118, 999,7174, 1068,7234, 1133,7298, + 1195,7365, 1252,7436, 1305,7511, 1354,7588, 1398,7668, 1437,7750, + 1472,7835, 1502,7921, 1526,8009, 1546,8098, 1560,8188, 1569,8279, + 1573,8370, 1571,8462, 1564,8553, 1552,8643, 1535,8733, 1512,8821, + 1485,8908, 1452,8994, 1414,9077, 1372,9158, 1325,9236, 1274,9312, + 1218,9384, 1158,9453, 1094,9518, 1027,9580, 956,9638, 882,9691, + 805,9740, 725,9784, 643,9824, 559,9859, 524,9872, 524,11100, + 610,11133, 693,11171, 773,11213, 852,11261, 927,11312, 999,11368, + 1068,11428, 1133,11492, 1195,11560, 1252,11631, 1305,11705, 1354,11782, + 1398,11862, 1437,11945, 1472,12029, 1502,12115, 1526,12203, 1546,12293, + 1560,12383, 1569,12474, 1573,12565, 1571,12656, 1564,12747, 1552,12838, + 1535,12927, 1512,13016, 1485,13103, 1452,13188, 1414,13271, 1372,13352, + 1325,13431, 1274,13506, 1218,13578, 1158,13647, 1094,13713, 1027,13774, + 956,13832, 882,13885, 805,13934, 725,13979, 643,14019, 559,14053, + 524,14066, 524,15295, 610,15327, 693,15365, 773,15408, 852,15455, + 927,15507, 999,15563, 1068,15623, 1133,15687, 1195,15754, 1252,15825, + 1305,15899, 1354,15977, 1398,16057, 1437,16139, 1472,16223, 1502,16310, + 1526,16398, 1546,16487, 1560,16577, 1569,16668, 1573,16759, 1571,16850, + 1564,16942, 1552,17032, 1535,17122, 1512,17210, 1485,17297, 1452,17382, + 1414,17466, 1372,17547, 1325,17625, 1274,17700, 1218,17773, 1158,17842, + 1094,17907, 1027,17969, 956,18026, 882,18080, 805,18129, 725,18173, + 643,18213, 559,18248, 524,18260, 524,19489, 610,19522, 693,19560, + 773,19602, 852,19649, 927,19701, 999,19757, 1068,19817, 1133,19881, + 1195,19948, 1252,20019, 1305,20094, 1354,20171, 1398,20251, 1437,20333, + 1472,20418, 1502,20504, 1526,20592, 1546,20681, 1560,20771, 1569,20862, + 1573,20954, 1571,21045, 1564,21136, 1552,21226, 1535,21316, 1512,21404, + 1485,21492, 1452,21577, 1414,21660, 1372,21741, 1325,21819, 1274,21895, + 1218,21967, 1158,22036, 1094,22101, 1027,22163, 956,22221, 882,22274, + 805,22323, 725,22368, 643,22407, 559,22442, 524,22455, 524,23683, + 610,23716, 693,23754, 773,23797, 852,23844, 927,23895, 999,23951, + 1068,24011, 1133,24075, 1195,24143, 1252,24214, 1305,24288, 1354,24365, + 1398,24445, 1437,24528, 1472,24612, 1502,24698, 1526,24786, 1546,24876, + 1560,24966, 1569,25057, 1573,25148, 1571,25239, 1564,25330, 1552,25421, + 1535,25510, 1512,25599, 1485,25686, 1452,25771, 1414,25854, 1372,25935, + 1325,26014, 1274,26089, 1218,26161, 1158,26230, 1094,26296, 1027,26357, + 956,26415, 882,26468, 805,26517, 725,26562, 643,26602, 559,26636, + 524,26649, 524,27878, 610,27910, 693,27948, 773,27991, 852,28038, + 927,28090, 999,28146, 1068,28206, 1133,28270, 1195,28337, 1252,28408, + 1305,28482, 1354,28560, 1398,28640, 1437,28722, 1472,28806, 1502,28893, + 1526,28981, 1546,29070, 1560,29160, 1569,29251, 1573,29342, 1571,29434, + 1564,29525, 1552,29615, 1535,29705, 1512,29793, 1485,29880, 1452,29965, + 1414,30049, 1372,30130, 1325,30208, 1274,30283, 1218,30356, 1158,30425, + 1094,30490, 1027,30552, 956,30609, 882,30663, 805,30712, 725,30756, + 643,30796, 559,30831, 524,30843, 524,32072, 609,32105, 692,32142, + 773,32185, 851,32232, 926,32283, 998,32339, 1066,32398, 1131,32462, + 1193,32529, 1250,32600, 1303,32674, 1352,32751, 1396,32830, 1436,32912, + 1470,32996, 1483,33031, 3131,33031, 3164,32945, 3202,32862, 3244,32781, + 3291,32703, 3343,32628, 3399,32556, 3459,32487, 3523,32422, 3591,32360, + 3662,32303, 3736,32250, 3813,32201, 3893,32157, 3975,32117, 4060,32083, + 4146,32053, 4234,32028, 4323,32009, 4413,31995, 4504,31986, 4596,31982, + 4687,31984, 4778,31991, 4868,32003, 4958,32020, 5047,32043, 5134,32070, + 5219,32103, 5302,32141, 5383,32183, 5461,32230, 5537,32281, 5609,32337, + 5678,32397, 5744,32460, 5805,32528, 5863,32599, 5916,32673, 5965,32750, + 6010,32830, 6049,32912, 6084,32996, 6097,33031, 7745,33031, 7778,32945, + 7815,32862, 7858,32781, 7905,32703, 7957,32628, 8013,32556, 8073,32487, + 8137,32422, 8204,32360, 8275,32303, 8350,32250, 8427,32201, 8507,32157, + 8589,32117, 8674,32083, 8760,32053, 8848,32028, 8937,32009, 9027,31995, + 9118,31986, 9209,31982, 9301,31984, 9392,31991, 9482,32003, 9572,32020, + 9660,32043, 9747,32070, 9833,32103, 9916,32141, 9997,32183, 10075,32230, + 10151,32281, 10223,32337, 10292,32397, 10357,32460, 10419,32528, 10477,32599, + 10530,32673, 10579,32750, 10623,32830, 10663,32912, 10698,32996, 10711,33031, + 12358,33031, 12391,32945, 12429,32862, 12472,32781, 12519,32703, 12571,32628, + 12627,32556, 12687,32487, 12750,32422, 12818,32360, 12889,32303, 12963,32250, + 13041,32201, 13120,32157, 13203,32117, 13287,32083, 13374,32053, 13462,32028, + 13551,32009, 13641,31995, 13732,31986, 13823,31982, 13914,31984, 14005,31991, + 14096,32003, 14186,32020, 14274,32043, 14361,32070, 14446,32103, 14530,32141, + 14611,32183, 14689,32230, 14764,32281, 14837,32337, 14906,32397, 14971,32460, + 15033,32528, 15090,32599, 15144,32673, 15193,32750, 15237,32830, 15277,32912, + 15312,32996, 15324,33031, 16972,33031, 17005,32945, 17043,32862, 17086,32781, + 17133,32703, 17184,32628, 17240,32556, 17300,32487, 17364,32422, 17432,32360, + 17503,32303, 17577,32250, 17654,32201, 17734,32157, 17817,32117, 17901,32083, + 17987,32053, 18075,32028, 18165,32009, 18255,31995, 18346,31986, 18437,31982, + 18528,31984, 18619,31991, 18710,32003, 18799,32020, 18888,32043, 18975,32070, + 19060,32103, 19143,32141, 19224,32183, 19303,32230, 19378,32281, 19450,32337, + 19519,32397, 19585,32460, 19646,32528, 19704,32599, 19757,32673, 19806,32750, + 19851,32830, 19891,32912, 19926,32996, 19938,33031, 21586,33031, 21619,32945, + 21657,32862, 21699,32781, 21747,32703, 21798,32628, 21854,32556, 21914,32487, + 21978,32422, 22046,32360, 22117,32303, 22191,32250, 22268,32201, 22348,32157, + 22430,32117, 22515,32083, 22601,32053, 22689,32028, 22778,32009, 22869,31995, + 22959,31986, 23051,31982, 23142,31984, 23233,31991, 23324,32003, 23413,32020, + 23502,32043, 23589,32070, 23674,32103, 23757,32141, 23838,32183, 23916,32230, + 23992,32281, 24064,32337, 24133,32397, 24199,32460, 24260,32528, 24318,32599, + 24371,32673, 24420,32750, 24465,32830, 24504,32912, 24539,32996, 24552,33031, + 26200,33031, 26233,32945, 26271,32862, 26313,32781, 26360,32703, 26412,32628, + 26468,32556, 26528,32487, 26592,32422, 26659,32360, 26730,32303, 26805,32250, + 26882,32201, 26962,32157, 27044,32117, 27129,32083, 27215,32053, 27303,32028, + 27392,32009, 27482,31995, 27573,31986, 27664,31982, 27756,31984, 27847,31991, + 27937,32003, 28027,32020, 28115,32043, 28202,32070, 28288,32103, 28371,32141, + 28452,32183, 28530,32230, 28606,32281, 28678,32337, 28747,32397, 28812,32460, + 28874,32528, 28932,32599, 28985,32673, 29034,32750, 29078,32830, 29118,32912, + 29153,32996, 29166,33031, 30814,33031, 30847,32945, 30884,32862, 30927,32781, + 30974,32703, 31026,32628, 31082,32556, 31142,32487, 31206,32422, 31273,32360, + 31344,32303, 31418,32250, 31496,32201, 31576,32157, 31658,32117, 31742,32083, + 31829,32053, 31917,32028, 32006,32009, 32096,31995, 32187,31986, 32278,31982, + 32370,31984, 32461,31991, 32551,32003, 32641,32020, 32729,32043, 32816,32070, + 32902,32103, 32985,32141, 33066,32183, 33144,32230, 33219,32281, 33292,32337, + 33361,32397, 33426,32460, 33488,32528, 33545,32599, 33599,32673, 33648,32750, + 33692,32830, 33732,32912, 33767,32996, 33779,33031, 35427,33031, 35460,32946, + 35498,32863, 35540,32782, 35587,32704, 35639,32629, 35694,32557, 35754,32489, + 35818,32423, 35885,32362, 35956,32305, 36029,32252, 36106,32203, 36186,32159, + 36268,32119, 36352,32084, 36386,32072, 36386,30843, 36301,30810, 36218,30773, + 36137,30730, 36059,30683, 35983,30631, 35911,30575, 35842,30515, 35777,30451, + 35716,30384, 35658,30313, 35605,30239, 35557,30161, 35512,30081, 35473,29999, + 35438,29915, 35409,29828, 35384,29740, 35364,29651, 35350,29561, 35341,29470, + 35338,29379, 35339,29287, 35346,29196, 35358,29106, 35376,29016, 35398,28928, + 35426,28841, 35458,28755, 35496,28672, 35538,28591, 35585,28513, 35637,28438, + 35692,28365, 35752,28296, 35816,28231, 35883,28169, 35954,28112, 36028,28058, + 36105,28009, 36185,27965, 36267,27925, 36352,27890, 36386,27878, 36386,26649, + 36301,26616, 36218,26578, 36137,26536, 36059,26489, 35983,26437, 35911,26381, + 35842,26321, 35777,26257, 35716,26189, 35658,26118, 35605,26044, 35557,25967, + 35512,25887, 35473,25805, 35438,25720, 35409,25634, 35384,25546, 35364,25457, + 35350,25366, 35341,25276, 35338,25184, 35339,25093, 35346,25002, 35358,24912, + 35376,24822, 35398,24733, 35426,24646, 35458,24561, 35496,24478, 35538,24397, + 35585,24319, 35637,24243, 35692,24171, 35752,24102, 35816,24036, 35883,23975, + 35954,23917, 36028,23864, 36105,23815, 36185,23770, 36267,23731, 36352,23696, + 36386,23683, 36386,22455, 36301,22422, 36218,22384, 36137,22341, 36059,22294, + 35983,22243, 35911,22187, 35842,22127, 35777,22063, 35716,21995, 35658,21924, + 35605,21850, 35557,21773, 35512,21693, 35473,21610, 35438,21526, 35409,21440, + 35384,21352, 35364,21262, 35350,21172, 35341,21081, 35338,20990, 35339,20899, + 35346,20808, 35358,20717, 35376,20628, 35398,20539, 35426,20452, 35458,20367, + 35496,20284, 35538,20203, 35585,20124, 35637,20049, 35692,19976, 35752,19907, + 35816,19842, 35883,19780, 35954,19723, 36028,19669, 36105,19620, 36185,19576, + 36267,19536, 36352,19501, 36386,19489, 36386,18260, 36301,18227, 36218,18190, + 36137,18147, 36059,18100, 35983,18048, 35911,17992, 35842,17932, 35777,17868, + 35716,17801, 35658,17730, 35605,17655, 35557,17578, 35512,17498, 35473,17416, + 35438,17332, 35409,17245, 35384,17157, 35364,17068, 35350,16978, 35341,16887, + 35338,16796, 35339,16704, 35346,16613, 35358,16523, 35376,16433, 35398,16345, + 35426,16258, 35458,16172, 35496,16089, 35538,16008, 35585,15930, 35637,15854, + 35692,15782, 35752,15713, 35816,15648, 35883,15586, 35954,15529, 36028,15475, + 36105,15426, 36185,15382, 36267,15342, 36352,15307, 36386,15295, 36386,14066, + 36301,14033, 36218,13995, 36137,13953, 36059,13906, 35983,13854, 35911,13798, + 35842,13738, 35777,13674, 35716,13606, 35658,13535, 35605,13461, 35557,13384, + 35512,13304, 35473,13222, 35438,13137, 35409,13051, 35384,12963, 35364,12874, + 35350,12783, 35341,12693, 35338,12601, 35339,12510, 35346,12419, 35358,12328, + 35376,12239, 35398,12150, 35426,12063, 35458,11978, 35496,11895, 35538,11814, + 35585,11736, 35637,11660, 35692,11588, 35752,11519, 35816,11453, 35883,11392, + 35954,11334, 36028,11281, 36105,11232, 36185,11187, 36267,11148, 36352,11113, + 36386,11100, 36386,9872, 36301,9839, 36218,9801, 36137,9758, 36059,9711, + 35983,9660, 35911,9604, 35842,9544, 35777,9480, 35716,9412, 35658,9341, + 35605,9267, 35557,9190, 35512,9110, 35473,9027, 35438,8943, 35409,8856, + 35384,8769, 35364,8679, 35350,8589, 35341,8498, 35338,8407, 35339,8316, + 35346,8225, 35358,8134, 35376,8045, 35398,7956, 35426,7869, 35458,7784, + 35496,7700, 35538,7620, 35585,7541, 35637,7466, 35692,7393, 35752,7324, + 35816,7259, 35883,7197, 35954,7140, 36028,7086, 36105,7037, 36185,6993, + 36267,6953, 36352,6918, 36386,6906, 36386,5677, 36301,5644, 36218,5607, + 36137,5564, 36059,5517, 35983,5465, 35911,5409, 35842,5349, 35777,5285, + 35716,5218, 35658,5147, 35605,5072, 35557,4995, 35512,4915, 35473,4833, + 35438,4748, 35409,4662, 35384,4574, 35364,4485, 35350,4395, 35341,4304, + 35338,4213, 35339,4121, 35346,4030, 35358,3940, 35376,3850, 35398,3762, + 35426,3675, 35458,3589, 35496,3506, 35538,3425, 35585,3347, 35637,3271, + 35692,3199, 35752,3130, 35816,3065, 35883,3003, 35954,2945, 36028,2892, + 36105,2843, 36185,2799, 36267,2759, 36352,2724, 36386,2711, 36386,1483, + 36301,1450, 36218,1413, 36138,1370, 36060,1323, 35985,1272, 35913,1216, + 35844,1156, 35779,1093, 35718,1026, 35660,955, 35607,881, 35558,804, + 35514,725, 35475,643, 35440,559, 35427,524, 33779,524, 33747,610, + 33709,693, 33666,773, 33619,852, 33567,927, 33511,999, 33451,1068, + 33387,1133, 33320,1195, 33249,1252, 33175,1305, 33097,1354, 33017,1398, + 32935,1437, 32851,1472, 32764,1502, 32676,1526, 32587,1546, 32497,1560, + 32406,1569, 32315,1573, 32223,1571, 32132,1564, 32042,1552, 31952,1535, + 31864,1512, 31777,1485, 31691,1452, 31608,1414, 31527,1372, 31449,1325, + 31374,1274, 31301,1218, 31232,1158, 31167,1094, 31105,1027, 31048,956, + 30994,882, 30945,805, 30901,725, 30861,643, 30826,559, 30814,524, + 29166,524, 29133,610, 29095,693, 29052,773, 29005,852, 28954,927, + 28898,999, 28838,1068, 28774,1133, 28706,1195, 28635,1252, 28561,1305, + 28484,1354, 28404,1398, 28321,1437, 28237,1472, 28150,1502, 28063,1526, + 27973,1546, 27883,1560, 27792,1569, 27701,1573, 27610,1571, 27519,1564, + 27428,1552, 27338,1535, 27250,1512, 27163,1485, 27078,1452, 26994,1414, + 26914,1372, 26835,1325, 26760,1274, 26687,1218, 26618,1158, 26553,1094, + 26491,1027, 26434,956, 26380,882, 26331,805, 26287,725, 26247,643, + 26212,559, 26200,524, 24552,524, 24519,610, 24481,693, 24439,773, + 24391,852, 24340,927, 24284,999, 24224,1068, 24160,1133, 24092,1195, + 24021,1252, 23947,1305, 23870,1354, 23790,1398, 23708,1437, 23623,1472, + 23537,1502, 23449,1526, 23360,1546, 23269,1560, 23178,1569, 23087,1573, + 22996,1571, 22905,1564, 22814,1552, 22725,1535, 22636,1512, 22549,1485, + 22464,1452, 22381,1414, 22300,1372, 22221,1325, 22146,1274, 22074,1218, + 22005,1158, 21939,1094, 21878,1027, 21820,956, 21767,882, 21718,805, + 21673,725, 21633,643, 21599,559, 21586,524, 19938,524, 19905,610, + 19867,693, 19825,773, 19778,852, 19726,927, 19670,999, 19610,1068, + 19546,1133, 19478,1195, 19407,1252, 19333,1305, 19256,1354, 19176,1398, + 19094,1437, 19009,1472, 18923,1502, 18835,1526, 18746,1546, 18656,1560, + 18565,1569, 18473,1573, 18382,1571, 18291,1564, 18201,1552, 18111,1535, + 18022,1512, 17935,1485, 17850,1452, 17767,1414, 17686,1372, 17608,1325, + 17532,1274, 17460,1218, 17391,1158, 17325,1094, 17264,1027, 17206,956, + 17153,882, 17104,805, 17059,725, 17020,643, 16985,559, 16972,524, + 15324,524, 15291,610, 15254,693, 15211,773, 15164,852, 15112,927, + 15056,999, 14996,1068, 14932,1133, 14865,1195, 14794,1252, 14719,1305, + 14642,1354, 14562,1398, 14480,1437, 14395,1472, 14309,1502, 14221,1526, + 14132,1546, 14042,1560, 13951,1569, 13860,1573, 13768,1571, 13677,1564, + 13587,1552, 13497,1535, 13409,1512, 13322,1485, 13236,1452, 13153,1414, + 13072,1372, 12994,1325, 12918,1274, 12846,1218, 12777,1158, 12712,1094, + 12650,1027, 12592,956, 12539,882, 12490,805, 12446,725, 12406,643, + 12371,559, 12358,524, 10711,524, 10678,610, 10640,693, 10597,773, + 10550,852, 10498,927, 10442,999, 10382,1068, 10319,1133, 10251,1195, + 10180,1252, 10106,1305, 10028,1354, 9949,1398, 9866,1437, 9782,1472, + 9695,1502, 9607,1526, 9518,1546, 9428,1560, 9337,1569, 9246,1573, + 9155,1571, 9064,1564, 8973,1552, 8883,1535, 8795,1512, 8708,1485, + 8623,1452, 8539,1414, 8458,1372, 8380,1325, 8305,1274, 8232,1218, + 8163,1158, 8098,1094, 8036,1027, 7979,956, 7925,882, 7876,805, + 7832,725, 7792,643, 7757,559, 7745,524, 6097,524, 6064,610, + 6026,693, 5983,773, 5936,852, 5885,927, 5829,999, 5769,1068, + 5705,1133, 5637,1195, 5566,1252, 5492,1305, 5415,1354, 5335,1398, + 5252,1437, 5168,1472, 5082,1502, 4994,1526, 4904,1546, 4814,1560, + 4723,1569, 4632,1573, 4541,1571, 4450,1564, 4359,1552, 4270,1535, + 4181,1512, 4094,1485, 4009,1452, 3926,1414, 3845,1372, 3766,1325, + 3691,1274, 3619,1218, 3550,1158, 3484,1094, 3423,1027, 3365,956, + 3312,882, 3263,805, 3218,725, 3178,643, 3143,559, 3131,524, + 1483,524, 1450,609, 1413,692, 1370,773, 1323,851, 1272,926, + 1216,998, 1156,1066, 1093,1131, 1026,1193, 955,1250, 881,1303, + 804,1352, 725,1396, 643,1436, 559,1470 + }), + MakePath({ -47877,-47877, 84788,-47877, 84788,81432, -47877,81432 }) }; Paths64 solution = InflatePaths(subject, -10000, JoinType::Round, EndType::Polygon); diff --git a/CPP/Utils/ClipFileSave.cpp b/CPP/Utils/ClipFileSave.cpp index ad0755d8..305484c6 100644 --- a/CPP/Utils/ClipFileSave.cpp +++ b/CPP/Utils/ClipFileSave.cpp @@ -2,13 +2,13 @@ // Functions load clipping operations from text files //------------------------------------------------------------------------------ -#include "ClipFileLoad.h" #include "ClipFileSave.h" #include #include -using namespace std; -using namespace Clipper2Lib; +namespace Clipper2Lib { + + using namespace std; //------------------------------------------------------------------------------ // Boyer Moore Horspool Search @@ -247,8 +247,8 @@ static bool GetInt(string::const_iterator& s_it, } bool SaveTest(const std::string& filename, bool append, - Clipper2Lib::Paths64* subj, Clipper2Lib::Paths64* subj_open, Clipper2Lib::Paths64* clip, - int64_t area, int64_t count, Clipper2Lib::ClipType ct, Clipper2Lib::FillRule fr) + const Paths64* subj, const Paths64* subj_open, const Paths64* clip, + int64_t area, int64_t count, ClipType ct, FillRule fr) { string line; bool found = false; @@ -258,7 +258,7 @@ bool SaveTest(const std::string& filename, bool append, { ifstream file; file.open(filename, std::ios::binary); - if (!file) return false; + if (!file || !file.good()) return false; BMH_Search bmh = BMH_Search(file, "CAPTION:"); while (bmh.FindNext()) ; if (bmh.LastFound()) @@ -322,3 +322,5 @@ bool SaveTest(const std::string& filename, bool append, source.close(); return true; } + +} //end namespace diff --git a/CPP/Utils/ClipFileSave.h b/CPP/Utils/ClipFileSave.h index 9aa253ae..2624b56c 100644 --- a/CPP/Utils/ClipFileSave.h +++ b/CPP/Utils/ClipFileSave.h @@ -5,10 +5,14 @@ #ifndef CLIPPER_TEST_SAVE_H #define CLIPPER_TEST_SAVE_H -#include "clipper2/clipper.h" +#include "ClipFileLoad.h" -bool SaveTest(const std::string& filename, bool append, - Clipper2Lib::Paths64* subj, Clipper2Lib::Paths64* subj_open, Clipper2Lib::Paths64* clip, - int64_t area, int64_t count, Clipper2Lib::ClipType ct, Clipper2Lib::FillRule fr); +namespace Clipper2Lib { + + bool SaveTest(const std::string& filename, bool append, + const Paths64* subj, const Paths64* subj_open, const Paths64* clip, + int64_t area, int64_t count, ClipType ct, FillRule fr); + +} //end namespace #endif //CLIPPER_TEST_SAVE_H diff --git a/CPP/Utils/clipper.svg.cpp b/CPP/Utils/clipper.svg.cpp index 337a07ab..c0a8754d 100644 --- a/CPP/Utils/clipper.svg.cpp +++ b/CPP/Utils/clipper.svg.cpp @@ -140,7 +140,7 @@ namespace Clipper2Lib { bool SvgWriter::SaveToFile(const std::string &filename, int max_width, int max_height, int margin) { - RectD rec = MaxInvalidRectD; + RectD rec = InvalidRectD; for (const PathInfo* pi : path_infos) for (const PathD& path : pi->paths_) for (const PointD& pt : path){ diff --git a/CSharp/Clipper2Lib/Clipper.Core.cs b/CSharp/Clipper2Lib/Clipper.Core.cs index b1f95333..6e35fb8a 100644 --- a/CSharp/Clipper2Lib/Clipper.Core.cs +++ b/CSharp/Clipper2Lib/Clipper.Core.cs @@ -342,6 +342,11 @@ public readonly bool IsEmpty() return bottom <= top || right <= left; } + public readonly bool IsValid() + { + return left < long.MaxValue; + } + public readonly Point64 MidPoint() { return new Point64((left + right) /2, (top + bottom)/2); diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index a1bd64c7..32d6961b 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 October 2023 * +* Date : 8 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -10,6 +10,8 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using Clipper2Lib; namespace Clipper2Lib { @@ -36,26 +38,46 @@ public class ClipperOffset private class Group { internal Paths64 inPaths; - internal Path64 outPath; - internal Paths64 outPaths; + internal List boundsList; internal JoinType joinType; internal EndType endType; internal bool pathsReversed; + internal int lowestPathIdx; public Group(Paths64 paths, JoinType joinType, EndType endType = EndType.Polygon) { - inPaths = new Paths64(paths); this.joinType = joinType; this.endType = endType; - outPath = new Path64(); - outPaths = new Paths64(); + + bool isJoined = ((endType == EndType.Polygon) || (endType == EndType.Joined)); + inPaths = new Paths64(paths.Count); + foreach(Path64 path in paths) + inPaths.Add(Clipper.StripDuplicates(path, isJoined)); + + boundsList = new List(); + GetMultiBounds(inPaths, boundsList, endType); + lowestPathIdx = GetLowestPathIdx(boundsList); pathsReversed = false; + if (endType == EndType.Polygon) + pathsReversed = Clipper.Area(inPaths[lowestPathIdx]) < 0; } } private static readonly double Tolerance = 1.0E-12; + private static readonly Rect64 InvalidRect64 = + new Rect64(long.MaxValue, long.MaxValue, long.MinValue, long.MinValue); + private static readonly RectD InvalidRectD = + new RectD(double.MaxValue, double.MaxValue, double.MinValue, double.MinValue); + private static readonly long MAX_COORD = long.MaxValue >> 2; + private static readonly long MIN_COORD = -MAX_COORD; + + private static readonly string + coord_range_error = "Error: Coordinate range."; + private readonly List _groupList = new List(); + private readonly Path64 inPath = new Path64(); + private Path64 pathOut = new Path64(); private readonly PathD _normals = new PathD(); private readonly Paths64 _solution = new Paths64(); private double _groupDelta; //*0.5 for open paths; *-1.0 for negative areas @@ -112,26 +134,47 @@ public void AddPaths(Paths64 paths, JoinType joinType, EndType endType) _groupList.Add(new Group(paths, joinType, endType)); } + private int CalcSolutionCapacity() + { + int result = 0; + foreach (Group g in _groupList) + result += (g.endType == EndType.Joined) ? g.inPaths.Count * 2 : g.inPaths.Count; + return result; + } + private void ExecuteInternal(double delta) { _solution.Clear(); if (_groupList.Count == 0) return; + _solution.Capacity = CalcSolutionCapacity(); + // make sure the offset delta is significant if (Math.Abs(delta) < 0.5) { foreach (Group group in _groupList) foreach (Path64 path in group.inPaths) _solution.Add(path); + return; } - else - { - _delta = delta; - _mitLimSqr = (MiterLimit <= 1 ? - 2.0 : 2.0 / Clipper.Sqr(MiterLimit)); - foreach (Group group in _groupList) - DoGroupOffset(group); - } + _delta = delta; + _mitLimSqr = (MiterLimit <= 1 ? + 2.0 : 2.0 / Clipper.Sqr(MiterLimit)); + + foreach (Group group in _groupList) + DoGroupOffset(group); + } + + internal bool CheckPathsReversed() + { + bool result = false; + foreach (Group g in _groupList) + if (g.endType == EndType.Polygon) + { + result = g.pathsReversed; + break; + } + return result; } public void Execute(double delta, Paths64 solution) @@ -140,44 +183,39 @@ public void Execute(double delta, Paths64 solution) ExecuteInternal(delta); if (_groupList.Count == 0) return; + bool pathsReversed = CheckPathsReversed(); + FillRule fillRule = pathsReversed ? FillRule.Negative : FillRule.Positive; + // clean up self-intersections ... - Clipper64 c = new Clipper64() - { - PreserveCollinear = PreserveCollinear, - // the solution should retain the orientation of the input - ReverseSolution = ReverseSolution != _groupList[0].pathsReversed - }; + Clipper64 c = new Clipper64(); + c.PreserveCollinear = PreserveCollinear; + // the solution should retain the orientation of the input + c.ReverseSolution = ReverseSolution != pathsReversed; #if USINGZ c.ZCallback = ZCallback; #endif c.AddSubject(_solution); - if (_groupList[0].pathsReversed) - c.Execute(ClipType.Union, FillRule.Negative, solution); - else - c.Execute(ClipType.Union, FillRule.Positive, solution); + c.Execute(ClipType.Union, fillRule, solution); } - public void Execute(double delta, PolyTree64 polytree) + public void Execute(double delta, PolyTree64 solutionTree) { - polytree.Clear(); + solutionTree.Clear(); ExecuteInternal(delta); if (_groupList.Count == 0) return; + bool pathsReversed = CheckPathsReversed(); + FillRule fillRule = pathsReversed ? FillRule.Negative : FillRule.Positive; // clean up self-intersections ... - Clipper64 c = new Clipper64() - { - PreserveCollinear = PreserveCollinear, - // the solution should retain the orientation of the input - ReverseSolution = ReverseSolution != _groupList[0].pathsReversed - }; + Clipper64 c = new Clipper64(); + c.PreserveCollinear = PreserveCollinear; + // the solution should normally retain the orientation of the input + c.ReverseSolution = ReverseSolution != pathsReversed; #if USINGZ c.ZCallback = ZCallback; #endif c.AddSubject(_solution); - if (_groupList[0].pathsReversed) - c.Execute(ClipType.Union, FillRule.Negative, polytree); - else - c.Execute(ClipType.Union, FillRule.Positive, polytree); + c.Execute(ClipType.Union, fillRule, solutionTree); } @@ -200,28 +238,64 @@ public void Execute(DeltaCallback64 deltaCallback, Paths64 solution) DeltaCallback = deltaCallback; Execute(1.0, solution); } - private static void GetBoundsAndLowestPolyIdx(Paths64 paths, - out int index, out Rect64 rec) + + internal static void GetMultiBounds(Paths64 paths, List boundsList, EndType endType) { - rec = new Rect64(false); // ie invalid rect - long lpX = long.MinValue; - index = -1; - for (int i = 0; i < paths.Count; i++) - foreach (Point64 pt in paths[i]) + + int minPathLen = (endType == EndType.Polygon) ? 3 : 1; + boundsList.Capacity = paths.Count; + foreach (Path64 path in paths) + { + if (path.Count < minPathLen) { - if (pt.Y >= rec.bottom) - { - if (pt.Y > rec.bottom || pt.X < lpX) - { - index = i; - lpX = pt.X; - rec.bottom = pt.Y; - } - } - else if (pt.Y < rec.top) rec.top = pt.Y; - if (pt.X > rec.right) rec.right = pt.X; - else if (pt.X < rec.left) rec.left = pt.X; + boundsList.Add(InvalidRect64); + continue; } + + Point64 pt1 = path[0]; + Rect64 r = new Rect64(pt1.X, pt1.Y, pt1.X, pt1.Y); + foreach (Point64 pt in path) + { + if (pt.Y > r.bottom) r.bottom = pt.Y; + else if (pt.Y < r.top) r.top = pt.Y; + if (pt.X > r.right) r.right = pt.X; + else if (pt.X < r.left) r.left = pt.X; + } + boundsList.Add(r); + } + } + + internal static bool ValidateBounds(List boundsList, double delta) + { + int int_delta = (int)delta; + + Point64 botPt = new Point64(int.MaxValue, int.MinValue); + foreach (Rect64 r in boundsList) + { + if (!r.IsValid()) continue; // ignore invalid paths + else if (r.left < MIN_COORD + int_delta || + r.right > MAX_COORD + int_delta || + r.top < MIN_COORD + int_delta || + r.bottom > MAX_COORD + int_delta) return false; + } + return true; + } + + internal static int GetLowestPathIdx(List boundsList) + { + int result = -1; + Point64 botPt = new Point64(long.MaxValue, long.MinValue); + for (int i = 0; i < boundsList.Count; i++) + { + Rect64 r = boundsList[i]; + if (!r.IsValid()) continue; // ignore invalid paths + else if (r.bottom > botPt.Y || (r.bottom == botPt.Y && r.left < botPt.X)) + { + botPt = new Point64(r.left, r.bottom); + result = i; + } + } + return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -326,7 +400,7 @@ private PointD GetPerpendicD(Point64 pt, PointD norm) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DoBevel(Group group, Path64 path, int j, int k) + private void DoBevel(Path64 path, int j, int k) { Point64 pt1, pt2; if (j == k) @@ -340,12 +414,12 @@ private void DoBevel(Group group, Path64 path, int j, int k) pt1 = new Point64(path[j].X + _groupDelta * _normals[k].x, path[j].Y + _groupDelta * _normals[k].y); pt2 = new Point64(path[j].X + _groupDelta * _normals[j].x, path[j].Y + _groupDelta * _normals[j].y); } - group.outPath.Add(pt1); - group.outPath.Add(pt2); + pathOut.Add(pt1); + pathOut.Add(pt2); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DoSquare(Group group, Path64 path, int j, int k) + private void DoSquare(Path64 path, int j, int k) { PointD vec; if (j == k) @@ -380,8 +454,8 @@ private void DoSquare(Group group, Path64 path, int j, int k) pt.z = ptQ.z; #endif //get the second intersect point through reflecion - group.outPath.Add(new Point64(ReflectPoint(pt, ptQ))); - group.outPath.Add(new Point64(pt)); + pathOut.Add(new Point64(ReflectPoint(pt, ptQ))); + pathOut.Add(new Point64(pt)); } else { @@ -390,9 +464,9 @@ private void DoSquare(Group group, Path64 path, int j, int k) #if USINGZ pt.z = ptQ.z; #endif - group.outPath.Add(new Point64(pt)); + pathOut.Add(new Point64(pt)); //get the second intersect point through reflecion - group.outPath.Add(new Point64(ReflectPoint(pt, ptQ))); + pathOut.Add(new Point64(ReflectPoint(pt, ptQ))); } } @@ -401,19 +475,19 @@ private void DoMiter(Group group, Path64 path, int j, int k, double cosA) { double q = _groupDelta / (cosA + 1); #if USINGZ - group.outPath.Add(new Point64( + pathOut.Add(new Point64( path[j].X + (_normals[k].x + _normals[j].x) * q, path[j].Y + (_normals[k].y + _normals[j].y) * q, path[j].Z)); #else - group.outPath.Add(new Point64( + pathOut.Add(new Point64( path[j].X + (_normals[k].x + _normals[j].x) * q, path[j].Y + (_normals[k].y + _normals[j].y) * q)); #endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DoRound(Group group, Path64 path, int j, int k, double angle) + private void DoRound(Path64 path, int j, int k, double angle) { if (DeltaCallback != null) { @@ -434,9 +508,9 @@ private void DoRound(Group group, Path64 path, int j, int k, double angle) PointD offsetVec = new PointD(_normals[k].x * _groupDelta, _normals[k].y * _groupDelta); if (j == k) offsetVec.Negate(); #if USINGZ - group.outPath.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y, pt.Z)); + pathOut.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y, pt.Z)); #else - group.outPath.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y)); + pathOut.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y)); #endif int steps = (int) Math.Ceiling(_stepsPerRad * Math.Abs(angle)); for (int i = 1; i < steps; i++) // ie 1 less than steps @@ -444,12 +518,12 @@ private void DoRound(Group group, Path64 path, int j, int k, double angle) offsetVec = new PointD(offsetVec.x * _stepCos - _stepSin * offsetVec.y, offsetVec.x * _stepSin + offsetVec.y * _stepCos); #if USINGZ - group.outPath.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y, pt.Z)); + pathOut.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y, pt.Z)); #else - group.outPath.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y)); + pathOut.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y)); #endif } - group.outPath.Add(GetPerpendic(pt, _normals[j])); + pathOut.Add(GetPerpendic(pt, _normals[j])); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -483,18 +557,18 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) } if (Math.Abs(_groupDelta) < Tolerance) { - group.outPath.Add(path[j]); + pathOut.Add(path[j]); return; } if (cosA > -0.99 && (sinA * _groupDelta < 0)) // test for concavity first (#593) { // is concave - group.outPath.Add(GetPerpendic(path[j], _normals[k])); + pathOut.Add(GetPerpendic(path[j], _normals[k])); // this extra point is the only (simple) way to ensure that // path reversals are fully cleaned with the trailing clipper - group.outPath.Add(path[j]); // (#405) - group.outPath.Add(GetPerpendic(path[j], _normals[j])); + pathOut.Add(path[j]); // (#405) + pathOut.Add(GetPerpendic(path[j], _normals[j])); } else if (cosA > 0.999) DoMiter(group, path, j, k, cosA); @@ -502,15 +576,15 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) { // miter unless the angle is so acute the miter would exceeds ML if (cosA > _mitLimSqr - 1) DoMiter(group, path, j, k, cosA); - else DoSquare(group, path, j, k); + else DoSquare(path, j, k); } else if (cosA > 0.99 || _joinType == JoinType.Bevel) //angle less than 8 degrees or a squared join - DoBevel(group, path, j, k); + DoBevel(path, j, k); else if (_joinType == JoinType.Round) - DoRound(group, path, j, k, Math.Atan2(sinA, cosA)); + DoRound(path, j, k, Math.Atan2(sinA, cosA)); else - DoSquare(group, path, j, k); + DoSquare(path, j, k); k = j; } @@ -518,22 +592,11 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void OffsetPolygon(Group group, Path64 path) { - // when the path is contracting, make sure - // there is sufficient space to do so. //#593 - //nb: this will have a small impact on performance - double a = Clipper.Area(path); - if ((a < 0) != (_groupDelta < 0)) - { - Rect64 rec = Clipper.GetBounds(path); - double offsetMinDim = Math.Abs(_groupDelta) * 2; - if (offsetMinDim > rec.Width || offsetMinDim > rec.Height) return; - } - - group.outPath = new Path64(); + pathOut = new Path64(); int cnt = path.Count, prev = cnt - 1; for (int i = 0; i < cnt; i++) OffsetPoint(group, path, i, ref prev); - group.outPaths.Add(group.outPath); + _solution.Add(pathOut); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -547,7 +610,7 @@ private void OffsetOpenJoined(Group group, Path64 path) private void OffsetOpenPath(Group group, Path64 path) { - group.outPath = new Path64(); + pathOut = new Path64(); int highI = path.Count - 1; if (DeltaCallback != null) @@ -555,18 +618,18 @@ private void OffsetOpenPath(Group group, Path64 path) // do the line start cap if (Math.Abs(_groupDelta) < Tolerance) - group.outPath.Add(path[0]); + pathOut.Add(path[0]); else switch (_endType) { case EndType.Butt: - DoBevel(group, path, 0, 0); + DoBevel(path, 0, 0); break; case EndType.Round: - DoRound(group, path, 0, 0, Math.PI); + DoRound(path, 0, 0, Math.PI); break; default: - DoSquare(group, path, 0, 0); + DoSquare(path, 0, 0); break; } @@ -583,18 +646,18 @@ private void OffsetOpenPath(Group group, Path64 path) _groupDelta = DeltaCallback(path, _normals, highI, highI); // do the line end cap if (Math.Abs(_groupDelta) < Tolerance) - group.outPath.Add(path[highI]); + pathOut.Add(path[highI]); else switch (_endType) { case EndType.Butt: - DoBevel(group, path, highI, highI); + DoBevel(path, highI, highI); break; case EndType.Round: - DoRound(group, path, highI, highI, Math.PI); + DoRound(path, highI, highI, Math.PI); break; default: - DoSquare(group, path, highI, highI); + DoSquare(path, highI, highI); break; } @@ -602,35 +665,28 @@ private void OffsetOpenPath(Group group, Path64 path) for (int i = highI, k = 0; i > 0; i--) OffsetPoint(group, path, i, ref k); - group.outPaths.Add(group.outPath); + _solution.Add(pathOut); } private void DoGroupOffset(Group group) { if (group.endType == EndType.Polygon) { - // the lowermost polygon must be an outer polygon. So we can use that as the - // designated orientation for outer polygons (needed for tidy-up clipping) - GetBoundsAndLowestPolyIdx(group.inPaths, - out int lowestIdx, out Rect64 grpBounds); - if (lowestIdx < 0) return; - double area = Clipper.Area(group.inPaths[lowestIdx]); - //if (area == 0) return; // this is probably unhelpful (#430) - group.pathsReversed = (area < 0); - if (group.pathsReversed) _groupDelta = -_delta; - else _groupDelta = _delta; + if (group.lowestPathIdx < 0) return; + //if (area == 0) return; // probably unhelpful (#430) + _groupDelta = (group.pathsReversed) ? -_delta : _delta; } else - { - group.pathsReversed = false; _groupDelta = Math.Abs(_delta) * 0.5; - } + double absDelta = Math.Abs(_groupDelta); + if (!ValidateBounds(group.boundsList, absDelta)) + throw new Exception(coord_range_error); + _joinType = group.joinType; _endType = group.endType; - if (DeltaCallback == null && - (group.joinType == JoinType.Round || group.endType == EndType.Round)) + if (group.joinType == JoinType.Round || group.endType == EndType.Round) { // calculate a sensible number of steps (for 360 deg for the given offset // arcTol - when fArcTolerance is undefined (0), the amount of @@ -647,59 +703,59 @@ private void DoGroupOffset(Group group) _stepsPerRad = stepsPer360 / (2 * Math.PI); } - bool isJoined = - (group.endType == EndType.Joined) || - (group.endType == EndType.Polygon); - + int i = 0; foreach (Path64 p in group.inPaths) { - Path64 path = Clipper.StripDuplicates(p, isJoined); - int cnt = path.Count; + Rect64 pathBounds = group.boundsList[i++]; + if (!pathBounds.IsValid()) continue; + + int cnt = p.Count; if ((cnt == 0) || ((cnt < 3) && (_endType == EndType.Polygon))) continue; + pathOut = new Path64(); if (cnt == 1) { - group.outPath = new Path64(); + Point64 pt = p[0]; + // single vertex so build a circle or square ... if (group.endType == EndType.Round) { double r = absDelta; int steps = (int)Math.Ceiling(_stepsPerRad * 2 * Math.PI); - group.outPath = Clipper.Ellipse(path[0], r, r, steps); + pathOut = Clipper.Ellipse(pt, r, r, steps); #if USINGZ - group.outPath = InternalClipper.SetZ(group.outPath, path[0].Z); + pathOut = InternalClipper.SetZ(pathOut, pt.Z); #endif } else { int d = (int) Math.Ceiling(_groupDelta); - Rect64 r = new Rect64(path[0].X - d, path[0].Y - d, - path[0].X + d, path[0].Y + d); - group.outPath = r.AsPath(); + Rect64 r = new Rect64(pt.X - d, pt.Y - d, pt.X + d, pt.Y + d); + pathOut = r.AsPath(); #if USINGZ - group.outPath = InternalClipper.SetZ(group.outPath, path[0].Z); + pathOut = InternalClipper.SetZ(pathOut, pt.Z); #endif } - group.outPaths.Add(group.outPath); - } - else - { - if (cnt == 2 && group.endType == EndType.Joined) - { - if (group.joinType == JoinType.Round) - _endType = EndType.Round; - else - _endType = EndType.Square; - } - BuildNormals(path); - if (_endType == EndType.Polygon) OffsetPolygon(group, path); - else if (_endType == EndType.Joined) OffsetOpenJoined(group, path); - else OffsetOpenPath(group, path); - } + _solution.Add(pathOut); + continue; + } // end of offsetting a single (open path) point + + // when shrinking, then make sure the path can shrink that far (#593) + if (_groupDelta < 0 && + Math.Min(pathBounds.Width, pathBounds.Height) < -_groupDelta * 2) + continue; + + if (cnt == 2 && group.endType == EndType.Joined) + _endType = (group.joinType == JoinType.Round) ? + EndType.Round : + EndType.Square; + + BuildNormals(p); + if (_endType == EndType.Polygon) OffsetPolygon(group, p); + else if (_endType == EndType.Joined) OffsetOpenJoined(group, p); + else OffsetOpenPath(group, p); } - _solution.AddRange(group.outPaths); - group.outPaths.Clear(); } } diff --git a/Delphi/Clipper2Lib/Clipper.Core.pas b/Delphi/Clipper2Lib/Clipper.Core.pas index 737cb383..1ed1e243 100644 --- a/Delphi/Clipper2Lib/Clipper.Core.pas +++ b/Delphi/Clipper2Lib/Clipper.Core.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 24 October 2023 * +* Date : 5 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library module * @@ -64,6 +64,7 @@ TPointD = record function GetWidth: Int64; {$IFDEF INLINING} inline; {$ENDIF} function GetHeight: Int64; {$IFDEF INLINING} inline; {$ENDIF} function GetIsEmpty: Boolean; {$IFDEF INLINING} inline; {$ENDIF} + function GetIsValid: Boolean; {$IFDEF INLINING} inline; {$ENDIF} function GetMidPoint: TPoint64; {$IFDEF INLINING} inline; {$ENDIF} public Left : Int64; @@ -78,6 +79,7 @@ TPointD = record property Width: Int64 read GetWidth; property Height: Int64 read GetHeight; property IsEmpty: Boolean read GetIsEmpty; + property IsValid: Boolean read GetIsValid; property MidPoint: TPoint64 read GetMidPoint; end; @@ -86,6 +88,7 @@ TPointD = record function GetWidth: double; {$IFDEF INLINING} inline; {$ENDIF} function GetHeight: double; {$IFDEF INLINING} inline; {$ENDIF} function GetIsEmpty: Boolean; {$IFDEF INLINING} inline; {$ENDIF} + function GetIsValid: Boolean; {$IFDEF INLINING} inline; {$ENDIF} function GetMidPoint: TPointD; {$IFDEF INLINING} inline; {$ENDIF} public Left : double; @@ -99,6 +102,7 @@ TPointD = record property Width: double read GetWidth; property Height: double read GetHeight; property IsEmpty: Boolean read GetIsEmpty; + property IsValid: Boolean read GetIsValid; property MidPoint: TPointD read GetMidPoint; end; @@ -335,6 +339,7 @@ procedure CheckPrecisionRange(var precision: integer); const MaxInt64 = 9223372036854775807; + MinInt64 = -MaxInt64; MaxCoord = MaxInt64 div 4; MinCoord = - MaxCoord; invalid64 = MaxInt64; @@ -346,6 +351,11 @@ procedure CheckPrecisionRange(var precision: integer); InvalidPtD : TPointD = (X: invalidD; Y: invalidD); NullRectD : TRectD = (left: 0; top: 0; right: 0; Bottom: 0); + InvalidRect64 : TRect64 = + (left: invalid64; top: invalid64; right: invalid64; bottom: invalid64); + InvalidRectD : TRectD = + (left: invalidD; top: invalidD; right: invalidD; bottom: invalidD); + Tolerance : Double = 1.0E-12; //https://github.com/AngusJohnson/Clipper2/discussions/564 @@ -378,6 +388,12 @@ function TRect64.GetIsEmpty: Boolean; end; //------------------------------------------------------------------------------ +function TRect64.GetIsValid: Boolean; +begin + result := left <> invalid64; +end; +//------------------------------------------------------------------------------ + function TRect64.GetMidPoint: TPoint64; begin result := Point64((Left + Right) div 2, (Top + Bottom) div 2); @@ -450,6 +466,12 @@ function TRectD.GetIsEmpty: Boolean; end; //------------------------------------------------------------------------------ +function TRectD.GetIsValid: Boolean; +begin + result := left <> invalidD; +end; +//------------------------------------------------------------------------------ + function TRectD.GetMidPoint: TPointD; begin result := PointD((Left + Right) *0.5, (Top + Bottom) *0.5); diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index f3bf669a..1ec5f664 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 24 September 2023 * +* Date : 8 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -32,13 +32,16 @@ interface TDeltaCallback64 = function (const path: TPath64; const path_norms: TPathD; currIdx, prevIdx: integer): double of object; + TRect64Array = array of TRect64; TGroup = class paths : TPaths64; - reversed : Boolean; joinType : TJoinType; endType : TEndType; - constructor Create(jt: TJoinType; et: TEndType); + reversed : Boolean; + lowestPathIdx: integer; + boundsList: TRect64Array; + constructor Create(const pathsIn: TPaths64; jt: TJoinType; et: TEndType); end; TClipperOffset = class @@ -58,9 +61,10 @@ TClipperOffset = class fGroupList : TListEx; fInPath : TPath64; fOutPath : TPath64; - fOutPaths : TPaths64; fOutPathLen : Integer; fSolution : TPaths64; + fSolutionLen : Integer; + fSolutionTree : TPolyTree64; fPreserveCollinear : Boolean; fReverseSolution : Boolean; fDeltaCallback64 : TDeltaCallback64; @@ -83,6 +87,10 @@ TClipperOffset = class procedure OffsetPolygon; procedure OffsetOpenJoined; procedure OffsetOpenPath; + function CalcSolutionCapacity: integer; + procedure UpdateSolution; {$IFDEF INLINING} inline; {$ENDIF} + + function CheckReverseOrientation: Boolean; procedure ExecuteInternal(delta: Double); public constructor Create(miterLimit: double = 2.0; @@ -119,6 +127,10 @@ implementation uses Math; +resourcestring + rsClipper_CoordRangeError = + 'Offsetting will exceed the valid coordinate range'; + const TwoPi : Double = 2 * PI; InvTwoPi : Double = 1/(2 * PI); @@ -127,6 +139,83 @@ implementation // Miscellaneous offset support functions //------------------------------------------------------------------------------ +function GetMultiBounds(const paths: TPaths64; endType: TEndType): TRect64Array; +var + i,j, len, len2, minPathLen: integer; + path: TPath64; + pt1, pt: TPoint64; + r: TRect64; +begin + if endType = etPolygon then + minPathLen := 3 else + minPathLen := 1; + len := Length(paths); + SetLength(Result, len); + for i := 0 to len -1 do + begin + path := paths[i]; + len2 := Length(path); + if len2 < minPathLen then + begin + Result[i] := InvalidRect64; + continue; + end; + pt1 := path[0]; + r := Rect64(pt1.X, pt1.Y, pt1.X, pt1.Y); + for j := 1 to len2 -1 do + begin + pt := path[j]; + if (pt.y > r.bottom) then r.bottom := pt.y + else if (pt.y < r.top) then r.top := pt.y; + if (pt.x > r.right) then r.right := pt.x + else if (pt.x < r.left) then r.left := pt.x; + end; + Result[i] := r; + end; +end; +//------------------------------------------------------------------------------ + +function ValidateBounds(const boundsList: TRect64Array; delta: double): Boolean; +var + i: integer; + iDelta, big, small: Int64; +begin + Result := false; + iDelta := Round(delta); + big := MaxCoord - iDelta; + small := MinCoord + iDelta; + for i := 0 to High(boundsList) do + with boundsList[i] do + begin + if not IsValid then continue; // skip invalid paths + if (left < small) or (right > big) or + (top < small) or (bottom > big) then Exit; + end; + Result := true; +end; +//------------------------------------------------------------------------------ + +function GetLowestClosedPathIdx(const boundsList: TRect64Array): integer; +var + i: integer; + botPt: TPoint64; +begin + Result := -1; + botPt := Point64(MaxInt64, MinInt64); + for i := 0 to High(boundsList) do + with boundsList[i] do + begin + if not IsValid or IsEmpty then Continue; + if (bottom > botPt.y) or + ((bottom = botPt.Y) and (left < botPt.X)) then + begin + botPt := Point64(left, bottom); + Result := i; + end; + end; +end; +//------------------------------------------------------------------------------ + function DotProduct(const vec1, vec2: TPointD): double; {$IFDEF INLINING} inline; {$ENDIF} begin @@ -215,10 +304,31 @@ function UnsafeGet(List: TList; Index: Integer): Pointer; // TGroup methods //------------------------------------------------------------------------------ -constructor TGroup.Create(jt: TJoinType; et: TEndType); +constructor TGroup.Create(const pathsIn: TPaths64; jt: TJoinType; et: TEndType); +var + i, len: integer; + isJoined: boolean; begin Self.joinType := jt; Self.endType := et; + + isJoined := et in [etPolygon, etJoined]; + len := Length(pathsIn); + SetLength(paths, len); + for i := 0 to len -1 do + paths[i] := StripDuplicates(pathsIn[i], isJoined); + + boundsList := GetMultiBounds(paths, et); + if (et = etPolygon) then + begin + lowestPathIdx := GetLowestClosedPathIdx(boundsList); + reversed := (lowestPathIdx >= 0) and ( + Area(pathsIn[lowestPathIdx]) < 0); + end else + begin + lowestPathIdx := -1; + reversed := false; + end; end; //------------------------------------------------------------------------------ @@ -253,6 +363,7 @@ procedure TClipperOffset.Clear; TGroup(fGroupList[i]).Free; fGroupList.Clear; fSolution := nil; + fSolutionLen := 0; end; //------------------------------------------------------------------------------ @@ -274,8 +385,7 @@ procedure TClipperOffset.AddPaths(const paths: TPaths64; group: TGroup; begin if Length(paths) = 0 then Exit; - group := TGroup.Create(joinType, endType); - AppendPaths(group.paths, paths); + group := TGroup.Create(paths, joinType, endType); fGroupList.Add(group); end; //------------------------------------------------------------------------------ @@ -302,37 +412,35 @@ function GetPerpendicD(const pt: TPoint64; const norm: TPointD; delta: double): procedure TClipperOffset.DoGroupOffset(group: TGroup); var - i,j, len, lowestIdx, steps: Integer; - r, stepsPer360, arcTol, area: Double; + i,j, len, steps: Integer; + r, stepsPer360, arcTol: Double; absDelta: double; rec: TRect64; - isJoined: Boolean; + pt0: TPoint64; begin + if group.endType = etPolygon then begin - // the lowermost polygon must be an outer polygon. So we can use that as the - // designated orientation for outer polygons (needed for tidy-up clipping) - lowestIdx := GetLowestPolygonIdx(group.paths); - if lowestIdx < 0 then Exit; - // nb: don't use the default orientation here ... - area := Clipper.Core.Area(group.paths[lowestIdx]); - //if area = 0 then Exit; // this is probably unhelpful (#430) - group.reversed := (area < 0); - if group.reversed then fGroupDelta := -fDelta - else fGroupDelta := fDelta; + if (group.lowestPathIdx < 0) then Exit; + //if (area == 0) return; // probably unhelpful (#430) + if group.reversed then + fGroupDelta := -fDelta else + fGroupDelta := fDelta; end else begin - group.reversed := false; fGroupDelta := Abs(fDelta) * 0.5; end; + + absDelta := Abs(fGroupDelta); + if not ValidateBounds(group.boundsList, absDelta) then + Raise EClipper2LibException(rsClipper_CoordRangeError); + fJoinType := group.joinType; fEndType := group.endType; // calculate a sensible number of steps (for 360 deg for the given offset - if (not Assigned(fDeltaCallback64) and - (group.joinType = jtRound) or (group.endType = etRound)) then + if (group.joinType = jtRound) or (group.endType = etRound) then begin - absDelta := Abs(fGroupDelta); // arcTol - when fArcTolerance is undefined (0), the amount of // curve imprecision that's allowed is based on the size of the // offset (delta). Obviously very large offsets will almost always @@ -350,72 +458,60 @@ procedure TClipperOffset.DoGroupOffset(group: TGroup); fStepsPerRad := stepsPer360 / TwoPi; end; - fOutPaths := nil; - isJoined := fEndType in [etPolygon, etJoined]; for i := 0 to High(group.paths) do begin - fInPath := StripDuplicates(group.paths[i], IsJoined); - len := Length(fInPath); - if (len = 0) or ((len < 3) and (fEndType = etPolygon)) then - Continue; + if not group.boundsList[i].IsValid then Continue; + fInPath := group.paths[i]; fNorms := nil; - fOutPath := nil; - fOutPathLen := 0; //if a single vertex then build a circle or a square ... + len := Length(fInPath); if len = 1 then begin if fGroupDelta < 1 then Continue; - absDelta := Abs(fGroupDelta); + pt0 := fInPath[0]; if (group.endType = etRound) then begin r := absDelta; - with fInPath[0] do - begin - steps := Ceil(fStepsPerRad * TwoPi); //#617 - fOutPath := Path64(Ellipse(RectD(X-r, Y-r, X+r, Y+r), steps)); + steps := Ceil(fStepsPerRad * TwoPi); //#617 + fOutPath := Path64(Ellipse( + RectD(pt0.X-r, pt0.Y-r, pt0.X+r, pt0.Y+r), steps)); {$IFDEF USINGZ} - for j := 0 to high(fOutPath) do - fOutPath[j].Z := Z; + for j := 0 to high(fOutPath) do + fOutPath[j].Z := pt0.Z; {$ENDIF} - end; end else begin j := Round(absDelta); - with fInPath[0] do - begin - rec := Rect64(X -j, Y -j, X+j, Y+j); - fOutPath := rec.AsPath; + rec := Rect64(pt0.X -j, pt0.Y -j, pt0.X+j, pt0.Y+j); + fOutPath := rec.AsPath; {$IFDEF USINGZ} - for j := 0 to high(fOutPath) do - fOutPath[j].Z := Z; + for j := 0 to high(fOutPath) do + fOutPath[j].Z := pt0.Z; {$ENDIF} - end end; - AppendPath(fOutPaths, fOutPath); + UpdateSolution; Continue; - end else - begin - if (len = 2) and (group.endType = etJoined) then - begin - if fJoinType = jtRound then - fEndType := etRound else - fEndType := etSquare; - end; - BuildNormals; + end; + + // when shrinking, then make sure the path can shrink that far + if (fGroupDelta < 0) and + (Min(group.boundsList[i].Width, group.boundsList[i].Height) < + fGroupDelta *2) then Continue; - if fEndType = etPolygon then OffsetPolygon - else if fEndType = etJoined then OffsetOpenJoined - else OffsetOpenPath; + if (len = 2) and (group.endType = etJoined) then + begin + if fJoinType = jtRound then + fEndType := etRound else + fEndType := etSquare; end; - if fOutPathLen = 0 then Continue; - SetLength(fOutPath, fOutPathLen); - AppendPath(fOutPaths, fOutPath); + BuildNormals; + if fEndType = etPolygon then OffsetPolygon + else if fEndType = etJoined then OffsetOpenJoined + else OffsetOpenPath; end; - // finally copy the working 'outPaths' to the solution - AppendPaths(fSolution, fOutPaths); end; //------------------------------------------------------------------------------ @@ -431,38 +527,45 @@ procedure TClipperOffset.BuildNormals; end; //------------------------------------------------------------------------------ +procedure TClipperOffset.UpdateSolution; +begin + if fOutPathLen = 0 then Exit; + SetLength(fOutPath, fOutPathLen); + fSolution[fSolutionLen] := fOutPath; + inc(fSolutionLen); + fOutPath := nil; + fOutPathLen := 0; +end; +//------------------------------------------------------------------------------ + +function TClipperOffset.CalcSolutionCapacity: integer; +var + i: integer; +begin + Result := 0; + for i := 0 to fGroupList.Count -1 do + with TGroup(fGroupList[i]) do + if endType = etJoined then + inc(Result, Length(paths) *2) else + inc(Result, Length(paths)); +end; +//------------------------------------------------------------------------------ + procedure TClipperOffset.OffsetPolygon; var i,j: integer; - a, offsetMinDim: double; - rec: TRect64; begin - //when the path is contracting, make sure - //there is sufficient space to do so. //#593 - //nb: this will have a small impact on performance - a := Area(fInPath); - if (a < 0) <> (fGroupDelta < 0) then - begin - rec := GetBounds(fInPath); - offsetMinDim := Abs(fGroupDelta) * 2; - if (offsetMinDim >= rec.Width) or (offsetMinDim >= rec.Height) then Exit; - end; - j := high(fInPath); for i := 0 to high(fInPath) do OffsetPoint(i, j); + UpdateSolution; end; //------------------------------------------------------------------------------ procedure TClipperOffset.OffsetOpenJoined; begin OffsetPolygon; - SetLength(fOutPath, fOutPathLen); - AppendPath(fOutPaths, fOutPath); - fOutPath := nil; - fOutPathLen := 0; fInPath := ReversePath(fInPath); - // Rebuild normals // BuildNormals; fNorms := ReversePath(fNorms); fNorms := ShiftPath(fNorms, 1); @@ -523,16 +626,23 @@ procedure TClipperOffset.OffsetOpenPath; k := 0; for i := highI downto 1 do //and stop at 1! OffsetPoint(i, k); + + UpdateSolution; end; //------------------------------------------------------------------------------ procedure TClipperOffset.ExecuteInternal(delta: Double); var - i: integer; + i,j: integer; group: TGroup; + pathsReversed: Boolean; + fillRule: TFillRule; + dummy: TPaths64; begin fSolution := nil; + fSolutionLen := 0; if fGroupList.Count = 0 then Exit; + SetLength(fSolution, CalcSolutionCapacity); fMinLenSqrd := 1; if abs(delta) < Tolerance then @@ -541,7 +651,11 @@ procedure TClipperOffset.ExecuteInternal(delta: Double); for i := 0 to fGroupList.Count -1 do begin group := TGroup(fGroupList[i]); - AppendPaths(fSolution, group.paths); + for j := 0 to High(group.paths) do + begin + fSolution[fSolutionLen] := group.paths[i]; + inc(fSolutionLen); + end; end; Exit; end; @@ -558,45 +672,52 @@ procedure TClipperOffset.ExecuteInternal(delta: Double); group := TGroup(fGroupList[i]); DoGroupOffset(group); end; + SetLength(fSolution, fSolutionLen); + + pathsReversed := CheckReverseOrientation(); + if pathsReversed then + fillRule := frNegative else + fillRule := frPositive; // clean up self-intersections ... with TClipper64.Create do try PreserveCollinear := fPreserveCollinear; // the solution should retain the orientation of the input - ReverseSolution := - fReverseSolution <> TGroup(fGroupList[0]).reversed; + ReverseSolution := fReverseSolution <> pathsReversed; AddSubject(fSolution); - if TGroup(fGroupList[0]).reversed then - Execute(ctUnion, frNegative, fSolution) else - Execute(ctUnion, frPositive, fSolution); + if assigned(fSolutionTree) then + Execute(ctUnion, fillRule, fSolutionTree, dummy); + Execute(ctUnion, fillRule, fSolution); finally free; end; end; //------------------------------------------------------------------------------ +function TClipperOffset.CheckReverseOrientation: Boolean; +var + i: integer; +begin + Result := false; + // find the orientation of the first closed path + for i := 0 to fGroupList.Count -1 do + with TGroup(fGroupList[i]) do + if endType = etPolygon then + begin + Result := reversed; + break; + end; +end; +//------------------------------------------------------------------------------ + procedure TClipperOffset.Execute(delta: Double; out solution: TPaths64); begin - fSolution := nil; solution := nil; - ExecuteInternal(delta); + fSolutionTree := nil; if fGroupList.Count = 0 then Exit; - - // clean up self-intersections ... - with TClipper64.Create do - try - PreserveCollinear := fPreserveCollinear; - // the solution should retain the orientation of the input - ReverseSolution := - fReverseSolution <> TGroup(fGroupList[0]).reversed; - AddSubject(fSolution); - if TGroup(fGroupList[0]).reversed then - Execute(ctUnion, frNegative, solution) else - Execute(ctUnion, frPositive, solution); - finally - free; - end; + ExecuteInternal(delta); + solution := fSolution; end; //------------------------------------------------------------------------------ @@ -608,29 +729,12 @@ procedure TClipperOffset.Execute(DeltaCallback: TDeltaCallback64; out solution: //------------------------------------------------------------------------------ procedure TClipperOffset.Execute(delta: Double; polytree: TPolyTree64); -var - dummy: TPaths64; begin - fSolution := nil; if not Assigned(polytree) then Raise EClipper2LibException(rsClipper_PolyTreeErr); - + fSolutionTree := polytree; + fSolutionTree.Clear; ExecuteInternal(delta); - - // clean up self-intersections ... - with TClipper64.Create do - try - PreserveCollinear := fPreserveCollinear; - // the solution should retain the orientation of the input - ReverseSolution := - fReverseSolution <> TGroup(fGroupList[0]).reversed; - AddSubject(fSolution); - if TGroup(fGroupList[0]).reversed then - Execute(ctUnion, frNegative, polytree, dummy) else - Execute(ctUnion, frPositive, polytree, dummy); - finally - free; - end; end; //------------------------------------------------------------------------------ diff --git a/Delphi/Clipper2Lib/Clipper.inc b/Delphi/Clipper2Lib/Clipper.inc index 066d5dc7..5b15f920 100644 --- a/Delphi/Clipper2Lib/Clipper.inc +++ b/Delphi/Clipper2Lib/Clipper.inc @@ -14,21 +14,21 @@ {$DEFINE INLINING} {$MODE DELPHI} {$ELSE} - {$IF CompilerVersion < 14} + {$IF COMPILERVERSION < 14} Requires Delphi version 6 or above. {$IFEND} - {$IF CompilerVersion >= 18} //Delphi 2007 + {$IF COMPILERVERSION >= 18} //Delphi 2007 {$DEFINE RECORD_METHODS} {$DEFINE STRICT} - {$IF CompilerVersion >= 19} //Delphi 2009 + {$IF COMPILERVERSION >= 19} //Delphi 2009 //While "inlining" is supported from D2005, it's buggy (see QC41166) until D2009 {$DEFINE INLINING} - {$IFEND} - {$IF COMPILERVERSION >= 23} //Delphi XE2+ - {$DEFINE XPLAT_GENERICS} - {$DEFINE ROUNDINGMODE} - {$IF COMPILERVERSION >= 24} //Delphi XE3+ - {$LEGACYIFEND ON} + {$IF COMPILERVERSION >= 23} //Delphi XE2+ + {$DEFINE XPLAT_GENERICS} + {$DEFINE ROUNDINGMODE} + {$IF COMPILERVERSION >= 24} //Delphi XE3+ + {$LEGACYIFEND ON} + {$IFEND} {$IFEND} {$IFEND} {$IFEND}