From fefc7d831a8852f89475a0a5546934059bfd9ed0 Mon Sep 17 00:00:00 2001 From: aismann Date: Wed, 2 Nov 2022 00:10:57 +0100 Subject: [PATCH] Clipper2 1.06 --- thirdparty/clipper2/CMakeLists.txt | 3 + thirdparty/clipper2/clipper.core.h | 65 +- thirdparty/clipper2/clipper.engine.cpp | 342 +++++----- thirdparty/clipper2/clipper.engine.h | 290 +++++---- thirdparty/clipper2/clipper.export.h | 753 +++++++++++++++++++++++ thirdparty/clipper2/clipper.h | 232 ++++++- thirdparty/clipper2/clipper.minkowski.h | 11 +- thirdparty/clipper2/clipper.offset.cpp | 22 +- thirdparty/clipper2/clipper.offset.h | 3 +- thirdparty/clipper2/clipper.rectclip.cpp | 547 ++++++++++++++++ thirdparty/clipper2/clipper.rectclip.h | 50 ++ 11 files changed, 2012 insertions(+), 306 deletions(-) create mode 100644 thirdparty/clipper2/clipper.export.h create mode 100644 thirdparty/clipper2/clipper.rectclip.cpp create mode 100644 thirdparty/clipper2/clipper.rectclip.h diff --git a/thirdparty/clipper2/CMakeLists.txt b/thirdparty/clipper2/CMakeLists.txt index f8f8ac8c52..bf8be03b7b 100644 --- a/thirdparty/clipper2/CMakeLists.txt +++ b/thirdparty/clipper2/CMakeLists.txt @@ -8,10 +8,13 @@ add_library(${target_name} STATIC clipper.core.h clipper.engine.cpp clipper.engine.h + clipper_export.h clipper.h clipper.minkowski.h clipper.offset.cpp clipper.offset.h + clipper.rectclip.cpp + clipper.rectclip.h ) target_include_directories(${target_name} PUBLIC .) diff --git a/thirdparty/clipper2/clipper.core.h b/thirdparty/clipper2/clipper.core.h index f6b497bf78..733e8eb5db 100644 --- a/thirdparty/clipper2/clipper.core.h +++ b/thirdparty/clipper2/clipper.core.h @@ -1,7 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Version : Clipper2 - ver.1.0.5 * -* Date : 2 October 2022 * +* Date : 26 October 2022 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2022 * * Purpose : Core Clipper Library structures and functions * @@ -17,6 +16,7 @@ #include #include #include +#include namespace Clipper2Lib { @@ -68,7 +68,6 @@ struct Point { explicit Point(const Point& p) { Init(p.x, p.y, p.z); - z = 0; } Point operator * (const double scale) const @@ -116,7 +115,7 @@ struct Point { friend std::ostream& operator<<(std::ostream& os, const Point& point) { - os << point.x << "," << point.y << " "; + os << point.x << "," << point.y; return os; } #endif @@ -384,12 +383,12 @@ struct Rect { return result; } - bool Contains(const Point& pt) + bool Contains(const Point& pt) const { return pt.x > left && pt.x < right&& pt.y > top && pt.y < bottom; } - bool Contains(const Rect& rec) + bool Contains(const Rect& rec) const { return rec.left >= left && rec.right <= right && rec.top >= top && rec.bottom <= bottom; @@ -404,6 +403,12 @@ struct Rect { bool IsEmpty() const { return bottom <= top || right <= left; }; + bool Intersects(const Rect& rec) const + { + return (std::max(left, rec.left) < std::min(right, rec.right)) && + (std::max(top, rec.top) < std::min(bottom, rec.bottom)); + }; + friend std::ostream &operator<<(std::ostream &os, const Rect &rect) { os << "(" << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom @@ -412,6 +417,29 @@ struct Rect { } }; +template +inline Rect ScaleRect(const Rect& rect, double scale) +{ + Rect result; + + if constexpr (std::numeric_limits::is_integer && + !std::numeric_limits::is_integer) + { + result.left = static_cast(std::round(rect.left * scale)); + result.top = static_cast(std::round(rect.top * scale)); + result.right = static_cast(std::round(rect.right * scale)); + result.bottom = static_cast(std::round(rect.bottom * scale)); + } + else + { + result.left = rect.left * scale; + result.top = rect.top * scale; + result.right = rect.right * scale; + result.bottom = rect.bottom * scale; + } + return result; +} + // clipper2Exception --------------------------------------------------------- class Clipper2Exception : public std::exception { @@ -510,6 +538,31 @@ inline bool IsPositive(const Path& poly) return Area(poly) >= 0; } +inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b, + const Point64& seg2a, const Point64& seg2b, bool inclusive = false) +{ + if (inclusive) + { + double res1 = CrossProduct(seg1a, seg2a, seg2b); + double res2 = CrossProduct(seg1b, seg2a, seg2b); + if (res1 * res2 > 0) return false; + double res3 = CrossProduct(seg2a, seg1a, seg1b); + double res4 = CrossProduct(seg2b, seg1a, seg1b); + if (res3 * res4 > 0) return false; + return (res1 || res2 || res3 || res4); // ensures not collinear + } + else { + double dx1 = static_cast(seg1a.x - seg1b.x); + double dy1 = static_cast(seg1a.y - seg1b.y); + double dx2 = static_cast(seg2a.x - seg2b.x); + double dy2 = static_cast(seg2a.y - seg2b.y); + return (((dy1 * (seg2a.x - seg1a.x) - dx1 * (seg2a.y - seg1a.y)) * + (dy1 * (seg2b.x - seg1a.x) - dx1 * (seg2b.y - seg1a.y)) < 0) && + ((dy2 * (seg1a.x - seg2a.x) - dx2 * (seg1a.y - seg2a.y)) * + (dy2 * (seg1b.x - seg2a.x) - dx2 * (seg1b.y - seg2a.y)) < 0)); + } +} + enum class PointInPolygonResult { IsOn, IsInside, IsOutside }; diff --git a/thirdparty/clipper2/clipper.engine.cpp b/thirdparty/clipper2/clipper.engine.cpp index f2cf143f62..2f10965d1b 100644 --- a/thirdparty/clipper2/clipper.engine.cpp +++ b/thirdparty/clipper2/clipper.engine.cpp @@ -1,7 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Version : Clipper2 - ver.1.0.5 * -* Date : 2 October 2022 * +* Date : 26 October 2022 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2022 * * Purpose : This is the main polygon clipping module * @@ -114,19 +113,16 @@ namespace Clipper2Lib { return prev; } - inline bool IsFront(const Active& e) { return (&e == e.outrec->front_edge); } - inline bool IsInvalidPath(OutPt* op) { return (!op || op->next == op); } - /******************************************************************************* * Dx: 0(90deg) * * | * @@ -149,7 +145,9 @@ namespace Clipper2Lib { { if ((currentY == ae.top.y) || (ae.top.x == ae.bot.x)) return ae.top.x; else if (currentY == ae.bot.y) return ae.bot.x; - else return ae.bot.x + static_cast(std::round(ae.dx * (currentY - ae.bot.y))); + else return ae.bot.x + static_cast(std::nearbyint(ae.dx * (currentY - ae.bot.y))); + // nb: std::nearbyint (or std::round) substantially *improves* performance here + // as it greatly improves the likelihood of edge adjacency in ProcessIntersectList(). } @@ -605,10 +603,19 @@ namespace Clipper2Lib { Clear(); } + void ClipperBase::DeleteEdges(Active*& e) + { + while (e) + { + Active* e2 = e; + e = e->next_in_ael; + delete e2; + } + } void ClipperBase::CleanUp() { - while (actives_) DeleteFromAEL(*actives_); + DeleteEdges(actives_); scanline_list_ = std::priority_queue(); intersect_nodes_.clear(); DisposeAllOutRecs(); @@ -1133,6 +1140,8 @@ namespace Clipper2Lib { left_bound = new Active(); left_bound->bot = local_minima->vertex->pt; left_bound->curr_x = left_bound->bot.x; + left_bound->wind_cnt = 0, + left_bound->wind_cnt2 = 0, left_bound->wind_dx = -1, left_bound->vertex_top = local_minima->vertex->prev; // ie descending left_bound->top = left_bound->vertex_top->pt; @@ -1150,6 +1159,8 @@ namespace Clipper2Lib { right_bound = new Active(); right_bound->bot = local_minima->vertex->pt; right_bound->curr_x = right_bound->bot.x; + right_bound->wind_cnt = 0, + right_bound->wind_cnt2 = 0, right_bound->wind_dx = 1, right_bound->vertex_top = local_minima->vertex->next; // ie ascending right_bound->top = right_bound->vertex_top->pt; @@ -1483,21 +1494,6 @@ namespace Clipper2Lib { FixSelfIntersects(outrec); } - - inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b, - const Point64& seg2a, const Point64& seg2b) - { - double dx1 = static_cast(seg1a.x - seg1b.x); - double dy1 = static_cast(seg1a.y - seg1b.y); - double dx2 = static_cast(seg2a.x - seg2b.x); - double dy2 = static_cast(seg2a.y - seg2b.y); - return (((dy1 * (seg2a.x - seg1a.x) - dx1 * (seg2a.y - seg1a.y)) * - (dy1 * (seg2b.x - seg1a.x) - dx1 * (seg2b.y - seg1a.y)) < 0) && - ((dy2 * (seg1a.x - seg2a.x) - dx2 * (seg1a.y - seg2a.y)) * - (dy2 * (seg1b.x - seg2a.x) - dx2 * (seg1b.y - seg2a.y)) < 0)); - } - - OutPt* ClipperBase::DoSplitOp(OutPt* outRecOp, OutPt* splitOp) { OutPt* prevOp = splitOp->prev; @@ -2055,52 +2051,6 @@ namespace Clipper2Lib { return succeeded_; } - - bool ClipperBase::Execute(ClipType clip_type, - FillRule fill_rule, Paths64& solution_closed) - { - solution_closed.clear(); - if (ExecuteInternal(clip_type, fill_rule, false)) - BuildPaths(solution_closed, nullptr); - CleanUp(); - return succeeded_; - } - - - bool ClipperBase::Execute(ClipType clip_type, FillRule fill_rule, - Paths64& solution_closed, Paths64& solution_open) - { - solution_closed.clear(); - solution_open.clear(); - if (ExecuteInternal(clip_type, fill_rule, false)) - BuildPaths(solution_closed, &solution_open); - CleanUp(); - return succeeded_; - } - - - bool ClipperBase::Execute(ClipType clip_type, FillRule fill_rule, PolyTree64& polytree) - { - Paths64 dummy; - polytree.Clear(); - if (ExecuteInternal(clip_type, fill_rule, true)) - BuildTree(polytree, dummy); - CleanUp(); - return succeeded_; - } - - bool ClipperBase::Execute(ClipType clip_type, - FillRule fill_rule, PolyTree64& polytree, Paths64& solution_open) - { - polytree.Clear(); - solution_open.clear(); - if (ExecuteInternal(clip_type, fill_rule, true)) - BuildTree(polytree, solution_open); - CleanUp(); - return succeeded_; - } - - void ClipperBase::DoIntersections(const int64_t top_y) { if (BuildIntersectList(top_y)) @@ -3289,72 +3239,6 @@ namespace Clipper2Lib { } - - bool BuildPath(OutPt* op, bool reverse, bool isOpen, Path64& path) - { - if (op->next == op || (!isOpen && op->next == op->prev)) return false; - path.resize(0); - Point64 lastPt; - OutPt* op2; - if (reverse) - { - lastPt = op->pt; - op2 = op->prev; - } - else - { - op = op->next; - lastPt = op->pt; - op2 = op->next; - } - path.push_back(lastPt); - - while (op2 != op) - { - if (op2->pt != lastPt) - { - lastPt = op2->pt; - path.push_back(lastPt); - } - if (reverse) - op2 = op2->prev; - else - op2 = op2->next; - } - return true; - } - - - void ClipperBase::BuildPaths(Paths64& solutionClosed, Paths64* solutionOpen) - { - solutionClosed.resize(0); - solutionClosed.reserve(outrec_list_.size()); - if (solutionOpen) - { - solutionOpen->resize(0); - solutionOpen->reserve(outrec_list_.size()); - } - - for (OutRec* outrec : outrec_list_) - { - if (outrec->pts == nullptr) continue; - - Path64 path; - if (solutionOpen && outrec->is_open) - { - if (BuildPath(outrec->pts, ReverseSolution, true, path)) - solutionOpen->emplace_back(std::move(path)); - } - else - { - //closed paths should always return a Positive orientation - if (BuildPath(outrec->pts, ReverseSolution, false, path)) - solutionClosed.emplace_back(std::move(path)); - } - } - } - - inline bool Path1InsidePath2(const OutRec* or1, const OutRec* or2) { PointInPolygonResult result = PointInPolygonResult::IsOn; @@ -3385,7 +3269,40 @@ namespace Clipper2Lib { return result; } - + bool BuildPath64(OutPt* op, bool reverse, bool isOpen, Path64& path) + { + if (op->next == op || (!isOpen && op->next == op->prev)) return false; + path.resize(0); + Point64 lastPt; + OutPt* op2; + if (reverse) + { + lastPt = op->pt; + op2 = op->prev; + } + else + { + op = op->next; + lastPt = op->pt; + op2 = op->next; + } + path.push_back(lastPt); + + while (op2 != op) + { + if (op2->pt != lastPt) + { + lastPt = op2->pt; + path.push_back(lastPt); + } + if (reverse) + op2 = op2->prev; + else + op2 = op2->next; + } + return true; + } + bool ClipperBase::DeepCheckOwner(OutRec* outrec, OutRec* owner) { if (owner->bounds.IsEmpty()) owner->bounds = GetBounds(owner->path); @@ -3405,7 +3322,7 @@ namespace Clipper2Lib { if (split->splits && DeepCheckOwner(outrec, split)) return true; if (!split->path.size()) - BuildPath(split->pts, ReverseSolution, false, split->path); + BuildPath64(split->pts, ReverseSolution, false, split->path); if (split->bounds.IsEmpty()) split->bounds = GetBounds(split->path); if (split->bounds.Contains(outrec->bounds) && @@ -3431,8 +3348,36 @@ namespace Clipper2Lib { } } + void Clipper64::BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen) + { + solutionClosed.resize(0); + solutionClosed.reserve(outrec_list_.size()); + if (solutionOpen) + { + solutionOpen->resize(0); + solutionOpen->reserve(outrec_list_.size()); + } - void ClipperBase::BuildTree(PolyPath64& polytree, Paths64& open_paths) + for (OutRec* outrec : outrec_list_) + { + if (outrec->pts == nullptr) continue; + + Path64 path; + if (solutionOpen && outrec->is_open) + { + if (BuildPath64(outrec->pts, ReverseSolution, true, path)) + solutionOpen->emplace_back(std::move(path)); + } + else + { + //closed paths should always return a Positive orientation + if (BuildPath64(outrec->pts, ReverseSolution, false, path)) + solutionClosed.emplace_back(std::move(path)); + } + } + } + + void Clipper64::BuildTree64(PolyPath64& polytree, Paths64& open_paths) { polytree.Clear(); open_paths.resize(0); @@ -3445,13 +3390,13 @@ namespace Clipper2Lib { if (outrec->is_open) { Path64 path; - if (BuildPath(outrec->pts, ReverseSolution, true, path)) + if (BuildPath64(outrec->pts, ReverseSolution, true, path)) open_paths.push_back(path); continue; } - if (!BuildPath(outrec->pts, ReverseSolution, false, outrec->path)) - continue; + if (!BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)) + continue; if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path); outrec->owner = GetRealOutRec(outrec->owner); if (outrec->owner) DeepCheckOwner(outrec, outrec->owner); @@ -3468,12 +3413,12 @@ namespace Clipper2Lib { tmp->idx = tmp_idx; outrec = tmp; outrec->owner = GetRealOutRec(outrec->owner); - BuildPath(outrec->pts, ReverseSolution, false, outrec->path); + BuildPath64(outrec->pts, ReverseSolution, false, outrec->path); if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path); if (outrec->owner) DeepCheckOwner(outrec, outrec->owner); } - PolyPath64* owner_polypath; + PolyPath* owner_polypath; if (outrec->owner && outrec->owner->polypath) owner_polypath = outrec->owner->polypath; else @@ -3482,20 +3427,117 @@ namespace Clipper2Lib { } } - static void PolyPath64ToPolyPathD(const PolyPath64& polypath, PolyPathD& result) + bool BuildPathD(OutPt* op, bool reverse, bool isOpen, PathD& path, double inv_scale) { - for (auto child : polypath) + if (op->next == op || (!isOpen && op->next == op->prev)) return false; + path.resize(0); + Point64 lastPt; + OutPt* op2; + if (reverse) { - PolyPathD* res_child = result.AddChild( - Path64ToPathD(child->Polygon())); - PolyPath64ToPolyPathD(*child, *res_child); + lastPt = op->pt; + op2 = op->prev; + } + else + { + op = op->next; + lastPt = op->pt; + op2 = op->next; + } + path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale)); + + while (op2 != op) + { + if (op2->pt != lastPt) + { + lastPt = op2->pt; + path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale)); + } + if (reverse) + op2 = op2->prev; + else + op2 = op2->next; + } + return true; + } + + void ClipperD::BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen) + { + solutionClosed.resize(0); + solutionClosed.reserve(outrec_list_.size()); + if (solutionOpen) + { + solutionOpen->resize(0); + solutionOpen->reserve(outrec_list_.size()); + } + + for (OutRec* outrec : outrec_list_) + { + if (outrec->pts == nullptr) continue; + + PathD path; + if (solutionOpen && outrec->is_open) + { + if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_)) + solutionOpen->emplace_back(std::move(path)); + } + else + { + //closed paths should always return a Positive orientation + if (BuildPathD(outrec->pts, ReverseSolution, false, path, invScale_)) + solutionClosed.emplace_back(std::move(path)); + } } } - inline void Polytree64ToPolytreeD(const PolyPath64& polytree, PolyPathD& result) + void ClipperD::BuildTreeD(PolyPathD& polytree, PathsD& open_paths) { - result.Clear(); - PolyPath64ToPolyPathD(polytree, result); + polytree.Clear(); + open_paths.resize(0); + if (has_open_paths_) + open_paths.reserve(outrec_list_.size()); + + for (OutRec* outrec : outrec_list_) + { + if (!outrec || !outrec->pts) continue; + if (outrec->is_open) + { + PathD path; + if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_)) + open_paths.push_back(path); + continue; + } + + if (!BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)) + continue; + if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path); + outrec->owner = GetRealOutRec(outrec->owner); + if (outrec->owner) DeepCheckOwner(outrec, outrec->owner); + + // swap the order when a child preceeds its owner + // (because owners must preceed children in polytrees) + if (outrec->owner && outrec->idx < outrec->owner->idx) + { + OutRec* tmp = outrec->owner; + outrec_list_[outrec->owner->idx] = outrec; + outrec_list_[outrec->idx] = tmp; + size_t tmp_idx = outrec->idx; + outrec->idx = tmp->idx; + tmp->idx = tmp_idx; + outrec = tmp; + outrec->owner = GetRealOutRec(outrec->owner); + BuildPath64(outrec->pts, ReverseSolution, false, outrec->path); + if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path); + if (outrec->owner) DeepCheckOwner(outrec, outrec->owner); + } + + PolyPath* owner_polypath; + if (outrec->owner && outrec->owner->polypath) + owner_polypath = outrec->owner->polypath; + else + owner_polypath = &polytree; + outrec->polypath = owner_polypath->AddChild(outrec->path); + } } } // namespace clipper2lib diff --git a/thirdparty/clipper2/clipper.engine.h b/thirdparty/clipper2/clipper.engine.h index fd59e7f4c5..306afa1233 100644 --- a/thirdparty/clipper2/clipper.engine.h +++ b/thirdparty/clipper2/clipper.engine.h @@ -1,17 +1,16 @@ /******************************************************************************* * Author : Angus Johnson * -* Version : Clipper2 - ver.1.0.4 * -* Date : 4 September 2022 * +* Date : 26 October 2022 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2022 * * Purpose : This is the main polygon clipping module * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ -#ifndef clipper_engine_h -#define clipper_engine_h +#ifndef CLIPPER_ENGINE_H +#define CLIPPER_ENGINE_H -#define CLIPPER2_VERSION "1.0.0" +#define CLIPPER2_VERSION "1.0.6" #include #include @@ -69,16 +68,11 @@ namespace Clipper2Lib { } }; - template class PolyPath; - - using PolyPath64 = PolyPath; - using PolyPathD = PolyPath; - - template - using PolyTree = PolyPath; - using PolyTree64 = PolyTree; - using PolyTreeD = PolyTree; + class PolyPath64; + class PolyPathD; + using PolyTree64 = PolyPath64; + using PolyTreeD = PolyPathD; struct OutRec; typedef std::vector OutRecList; @@ -92,7 +86,7 @@ namespace Clipper2Lib { Active* front_edge = nullptr; Active* back_edge = nullptr; OutPt* pts = nullptr; - PolyPath64* polypath = nullptr; + PolyPath* polypath = nullptr; Rect64 bounds = {}; Path64 path; bool is_open = false; @@ -165,11 +159,9 @@ namespace Clipper2Lib { FillRule fillrule_ = FillRule::EvenOdd; FillRule fillpos = FillRule::Positive; int64_t bot_y_ = 0; - bool has_open_paths_ = false; bool minima_list_sorted_ = false; bool using_polytree_ = false; - bool succeeded_ = true; - Active *actives_ = nullptr; + Active* actives_ = nullptr; Active *sel_ = nullptr; Joiner *horz_joiners_ = nullptr; std::vector minima_list_; //pointers in case of memory reallocs @@ -177,7 +169,6 @@ namespace Clipper2Lib { std::vector vertex_lists_; std::priority_queue scanline_list_; std::vector intersect_nodes_; - std::vector outrec_list_; //pointers in case of memory reallocs std::vector joiner_list_; //pointers in case of memory reallocs void Reset(); void InsertScanline(int64_t y); @@ -185,6 +176,7 @@ namespace Clipper2Lib { bool PopLocalMinima(int64_t y, LocalMinima *&local_minima); void DisposeAllOutRecs(); void DisposeVerticesAndLocalMinima(); + void DeleteEdges(Active*& e); void AddLocMin(Vertex &vert, PathType polytype, bool is_open); bool IsContributingClosed(const Active &e) const; inline bool IsContributingOpen(const Active &e) const; @@ -230,11 +222,12 @@ namespace Clipper2Lib { void DeleteJoin(Joiner* joiner); void ProcessJoinerList(); OutRec* ProcessJoin(Joiner* joiner); + protected: + bool has_open_paths_ = false; + bool succeeded_ = true; + std::vector outrec_list_; //pointers in case list memory reallocated bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees); bool DeepCheckOwner(OutRec* outrec, OutRec* owner); - void BuildPaths(Paths64& solutionClosed, Paths64* solutionOpen); - void BuildTree(PolyPath64& polytree, Paths64& open_paths); - protected: #ifdef USINGZ ZCallback64 zCallback_ = nullptr; void SetZ(const Active& e1, const Active& e2, Point64& pt); @@ -242,14 +235,6 @@ namespace Clipper2Lib { void CleanUp(); // unlike Clear, CleanUp preserves added paths void AddPath(const Path64& path, PathType polytype, bool is_open); void AddPaths(const Paths64& paths, PathType polytype, bool is_open); - - bool Execute(ClipType clip_type, - FillRule fill_rule, Paths64& solution_closed); - bool Execute(ClipType clip_type, - FillRule fill_rule, Paths64& solution_closed, Paths64& solution_open); - bool Execute(ClipType clip_type, FillRule fill_rule, PolyTree64& polytree); - bool Execute(ClipType clip_type, - FillRule fill_rule, PolyTree64& polytree, Paths64& open_paths); public: virtual ~ClipperBase(); bool PreserveCollinear = true; @@ -264,55 +249,30 @@ namespace Clipper2Lib { //alternative Paths structure, it does preserve path 'ownership' - ie those //paths that contain (or own) other paths. This will be useful to some users. - template - class PolyPath final { - private: - double scale_; - Path polygon_; - std::vector childs_; + class PolyPath { protected: - const PolyPath* parent_; - PolyPath(const PolyPath* parent, - const Path& path) : - scale_(parent->scale_), polygon_(path), parent_(parent){} + PolyPath* parent_; public: - - explicit PolyPath(int precision = 0) // NB only for root node - { - scale_ = std::pow(10, precision); - parent_ = nullptr; - } - - ~PolyPath() { Clear(); }; - + PolyPath(PolyPath* parent = nullptr): parent_(parent){} + virtual ~PolyPath() { Clear(); }; //https://en.cppreference.com/w/cpp/language/rule_of_three PolyPath(const PolyPath&) = delete; PolyPath& operator=(const PolyPath&) = delete; - PolyPath* operator [] (size_t index) { return childs_[index]; } - - typename std::vector::const_iterator begin() const { return childs_.cbegin(); } - typename std::vector::const_iterator end() const { return childs_.cend(); } - - void Clear() { - for (PolyPath* child : childs_) delete child; - childs_.resize(0); - } - - void reserve(size_t size) + unsigned Level() const { - if (size > childs_.size()) childs_.reserve(size); + unsigned result = 0; + const PolyPath* p = parent_; + while (p) { ++result; p = p->parent_; } + return result; } - PolyPath* AddChild(const Path& path) - { - childs_.push_back(new PolyPath(this, path)); - return childs_.back(); - } + virtual PolyPath* AddChild(const Path64& path) = 0; - size_t Count() const { return childs_.size(); } + virtual void Clear() {}; + virtual size_t Count() const { return 0; } - const PolyPath* parent() const { return parent_; } + const PolyPath* Parent() const { return parent_; } bool IsHole() const { @@ -324,25 +284,134 @@ namespace Clipper2Lib { } return is_hole; } + }; - const Path& Polygon() const { return polygon_; } + class PolyPath64 : public PolyPath { + private: + std::vector childs_; + Path64 polygon_; + typedef typename std::vector::const_iterator pp64_itor; + public: + PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {} + PolyPath64* operator [] (size_t index) { return static_cast(childs_[index]); } + pp64_itor begin() const { return childs_.cbegin(); } + pp64_itor end() const { return childs_.cend(); } + + PolyPath64* AddChild(const Path64& path) override + { + PolyPath64* result = new PolyPath64(this); + childs_.push_back(result); + result->polygon_ = path; + return result; + } + + void Clear() override + { + for (const PolyPath64* child : childs_) delete child; + childs_.resize(0); + } + + size_t Count() const override + { + return childs_.size(); + } + + const Path64 Polygon() const { return polygon_; }; double Area() const { - double result = Clipper2Lib::Area(polygon_); - for (const PolyPath* child : childs_) + double result = Clipper2Lib::Area(polygon_); + for (const PolyPath64* child : childs_) result += child->Area(); return result; } + friend std::ostream& operator << (std::ostream& outstream, const PolyPath64& polypath) + { + const size_t level_indent = 4; + const size_t coords_per_line = 4; + const size_t last_on_line = coords_per_line - 1; + unsigned level = polypath.Level(); + if (level > 0) + { + std::string level_padding; + level_padding.insert(0, (level - 1) * level_indent, ' '); + std::string caption = polypath.IsHole() ? "Hole " : "Outer Polygon "; + std::string childs = polypath.Count() == 1 ? " child" : " children"; + outstream << level_padding.c_str() << caption << "with " << polypath.Count() << childs << std::endl; + outstream << level_padding; + size_t i = 0, highI = polypath.Polygon().size() - 1; + for (; i < highI; ++i) + { + outstream << polypath.Polygon()[i] << ' '; + if ((i % coords_per_line) == last_on_line) + outstream << std::endl << level_padding; + } + if (highI > 0) outstream << polypath.Polygon()[i]; + outstream << std::endl; + } + for (auto child : polypath) + outstream << *child; + return outstream; + } + }; + class PolyPathD : public PolyPath { + private: + std::vector childs_; + double inv_scale_; + PathD polygon_; + typedef typename std::vector::const_iterator ppD_itor; + public: + PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent) + { + inv_scale_ = parent ? parent->inv_scale_ : 1.0; + } + PolyPathD* operator [] (size_t index) + { + return static_cast(childs_[index]); + } + ppD_itor begin() const { return childs_.cbegin(); } + ppD_itor end() const { return childs_.cend(); } - void Polytree64ToPolytreeD(const PolyPath64& polytree, PolyPathD& result); + void SetInvScale(double value) { inv_scale_ = value; } + double InvScale() { return inv_scale_; } + PolyPathD* AddChild(const Path64& path) override + { + PolyPathD* result = new PolyPathD(this); + childs_.push_back(result); + result->polygon_ = ScalePath(path, inv_scale_); + return result; + } + void Clear() override + { + for (const PolyPathD* child : childs_) delete child; + childs_.resize(0); + } + + size_t Count() const override + { + return childs_.size(); + } + + const PathD Polygon() const { return polygon_; }; + + double Area() const + { + double result = Clipper2Lib::Area(polygon_); + for (const PolyPathD* child : childs_) + result += child->Area(); + return result; + } + }; class Clipper64 : public ClipperBase { + private: + void BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen); + void BuildTree64(PolyPath64& polytree, Paths64& open_paths); public: #ifdef USINGZ void SetZCallback(ZCallback64 cb) { zCallback_ = cb; } @@ -364,23 +433,38 @@ namespace Clipper2Lib { bool Execute(ClipType clip_type, FillRule fill_rule, Paths64& closed_paths) { - return ClipperBase::Execute(clip_type, fill_rule, closed_paths); + Paths64 dummy; + return Execute(clip_type, fill_rule, closed_paths, dummy); } - bool Execute(ClipType clip_type, - FillRule fill_rule, Paths64& closed_paths, Paths64& open_paths) + bool Execute(ClipType clip_type, FillRule fill_rule, + Paths64& closed_paths, Paths64& open_paths) { - return ClipperBase::Execute(clip_type, fill_rule, closed_paths, open_paths); + closed_paths.clear(); + open_paths.clear(); + if (ExecuteInternal(clip_type, fill_rule, false)) + BuildPaths64(closed_paths, &open_paths); + CleanUp(); + return succeeded_; } bool Execute(ClipType clip_type, FillRule fill_rule, PolyTree64& polytree) { - return ClipperBase::Execute(clip_type, fill_rule, polytree); + Paths64 dummy; + return Execute(clip_type, fill_rule, polytree, dummy); } + bool Execute(ClipType clip_type, FillRule fill_rule, PolyTree64& polytree, Paths64& open_paths) { - return ClipperBase::Execute(clip_type, fill_rule, polytree, open_paths); + if (ExecuteInternal(clip_type, fill_rule, true)) + { + open_paths.clear(); + polytree.Clear(); + BuildTree64(polytree, open_paths); + } + CleanUp(); + return succeeded_; } }; @@ -390,6 +474,8 @@ namespace Clipper2Lib { #ifdef USINGZ ZCallbackD zCallback_ = nullptr; #endif + void BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen); + void BuildTreeD(PolyPathD& polytree, PathsD& open_paths); public: explicit ClipperD(int precision = 2) : ClipperBase() { @@ -448,13 +534,8 @@ namespace Clipper2Lib { bool Execute(ClipType clip_type, FillRule fill_rule, PathsD& closed_paths) { -#ifdef USINGZ - CheckCallback(); -#endif - Paths64 closed_paths64; - if (!ClipperBase::Execute(clip_type, fill_rule, closed_paths64)) return false; - closed_paths = ScalePaths(closed_paths64, invScale_); - return true; + PathsD dummy; + return Execute(clip_type, fill_rule, closed_paths, dummy); } bool Execute(ClipType clip_type, @@ -463,40 +544,39 @@ namespace Clipper2Lib { #ifdef USINGZ CheckCallback(); #endif - Paths64 closed_paths64; - Paths64 open_paths64; - if (!ClipperBase::Execute(clip_type, - fill_rule, closed_paths64, open_paths64)) return false; - closed_paths = ScalePaths(closed_paths64, invScale_); - open_paths = ScalePaths(open_paths64, invScale_); - return true; + if (ExecuteInternal(clip_type, fill_rule, false)) + { + BuildPathsD(closed_paths, &open_paths); + } + CleanUp(); + return succeeded_; } bool Execute(ClipType clip_type, FillRule fill_rule, PolyTreeD& polytree) { -#ifdef USINGZ - CheckCallback(); -#endif - PolyTree64 tree_result; - if (!ClipperBase::Execute(clip_type, fill_rule, tree_result)) return false;; - Polytree64ToPolytreeD(tree_result, polytree); - return true; + PathsD dummy; + return Execute(clip_type, fill_rule, polytree, dummy); } bool Execute(ClipType clip_type, - FillRule fill_rule, PolyTreeD& polytree, Paths64& open_paths) + FillRule fill_rule, PolyTreeD& polytree, PathsD& open_paths) { #ifdef USINGZ CheckCallback(); #endif - PolyTree64 tree_result; - if (!ClipperBase::Execute(clip_type, fill_rule, tree_result, open_paths)) return false;; - Polytree64ToPolytreeD(tree_result, polytree); - return true; + if (ExecuteInternal(clip_type, fill_rule, true)) + { + polytree.Clear(); + polytree.SetInvScale(invScale_); + open_paths.clear(); + BuildTreeD(polytree, open_paths); + } + CleanUp(); + return succeeded_; } }; } // namespace -#endif // clipper_engine_h +#endif // CLIPPER_ENGINE_H diff --git a/thirdparty/clipper2/clipper.export.h b/thirdparty/clipper2/clipper.export.h new file mode 100644 index 0000000000..9988df5ea2 --- /dev/null +++ b/thirdparty/clipper2/clipper.export.h @@ -0,0 +1,753 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 28 October 2022 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2022 * +* Purpose : This module exports the Clipper2 Library (ie DLL/so) * +* License : http://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +// The exported functions below refer to simple structures that +// can be understood across multiple languages. Consequently +// Path64, PathD, Polytree64 etc are converted from classes +// (std::vector<> etc) into the following data structures: +// +// CPath64 (int64_t*) & CPathD (double_t*): +// Path64 and PathD are converted into arrays of x,y coordinates. +// However in these arrays the first x,y coordinate pair is a +// counter with 'x' containing the number of following coordinate +// pairs. ('y' must always be 0.) +//__________________________________ +// |counter|coord1|coord2|...|coordN| +// |N ,0 |x1, y1|x2, y2|...|xN, yN| +// __________________________________ +// +// CPaths64 (int64_t**) & CPathsD (double_t**): +// These are arrays of pointers to CPath64 and CPathD where +// the first pointer is to a 'counter path'. This 'counter +// path' has a single x,y coord pair where 'y' contains +// the number of paths that follow (and with 'x' always 0). +// _______________________________ +// |counter|path1|path2|...|pathN| +// |addr0 |addr1|addr2|...|addrN| (*addr0[0]=0; *addr0[1]=N) +// _______________________________ +// +// The structures of CPolytree64 and CPolytreeD are defined +// below and they don't need to be repeated or explained here. +// +// Finally, the pointer structures created and exported through +// these functions can't safely be destroyed externally, so +// a number of 'dispose functions are also exported. + +#ifndef CLIPPER2_EXPORT_H +#define CLIPPER2_EXPORT_H + +#include +#include + +#include "clipper2/clipper.core.h" +#include "clipper2/clipper.engine.h" +#include "clipper2/clipper.offset.h" +#include "clipper2/clipper.rectclip.h" + +namespace Clipper2Lib { + +typedef int64_t* CPath64; +typedef int64_t** CPaths64; +typedef double* CPathD; +typedef double** CPathsD; + +typedef struct CPolyPath64 { + CPath64 polygon; + uint32_t is_hole; + uint32_t child_count; + CPolyPath64* childs; +} +CPolyTree64; + +typedef struct CPolyPathD { + CPathD polygon; + uint32_t is_hole; + uint32_t child_count; + CPolyPathD* childs; +} +CPolyTreeD; + +template +struct CRect { + T left; + T top; + T right; + T bottom; +}; + +typedef CRect CRect64; +typedef CRect CRectD; + +template +inline bool CRectIsEmpty(const CRect& rect) +{ + return (rect.right <= rect.left) || (rect.bottom <= rect.top); +} + +template +inline Rect CRectToRect(const CRect& rect) +{ + Rect result; + result.left = rect.left; + result.top = rect.top; + result.right = rect.right; + result.bottom = rect.bottom; + return result; +} + +inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2); +inline CPath64 CreateCPath64(const Path64& p); +inline CPaths64 CreateCPaths64(const Paths64& pp); +inline Path64 ConvertCPath64(const CPath64& p); +inline Paths64 ConvertCPaths64(const CPaths64& pp); + +inline CPathD CreateCPathD(size_t cnt1, size_t cnt2); +inline CPathD CreateCPathD(const PathD& p); +inline CPathsD CreateCPathsD(const PathsD& pp); +inline PathD ConvertCPathD(const CPathD& p); +inline PathsD ConvertCPathsD(const CPathsD& pp); + +// the following function avoid multiple conversions +inline Path64 ConvertCPathD(const CPathD& p, double scale); +inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale); +inline CPathD CreateCPathD(const Path64& p, double scale); +inline CPathsD CreateCPathsD(const Paths64& pp, double scale); + +inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt); +inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale); + +#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport) + + +EXTERN_DLL_EXPORT const char* Version() +{ + return CLIPPER2_VERSION; +} + +EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p) +{ + if (p) delete[] p; +} + +EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& pp) +{ + if (!pp) return; + CPaths64 v = pp; + CPath64 cnts = *v; + const size_t cnt = static_cast(cnts[1]); + for (size_t i = 0; i <= cnt; ++i) //nb: cnt +1 + DisposeExportedCPath64(*v++); + delete[] pp; + pp = nullptr; +} + +EXTERN_DLL_EXPORT void DisposeExportedCPathD(CPathD p) +{ + if (p) delete[] p; +} + +EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& pp) +{ + if (!pp) return; + CPathsD v = pp; + CPathD cnts = *v; + size_t cnt = static_cast(cnts[1]); + for (size_t i = 0; i <= cnt; ++i) //nb: cnt +1 + DisposeExportedCPathD(*v++); + delete[] pp; + pp = nullptr; +} + +EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, + uint8_t fillrule, const CPaths64 subjects, + const CPaths64 subjects_open, const CPaths64 clips, + CPaths64& solution, CPaths64& solution_open, + bool preserve_collinear = true, bool reverse_solution = false) +{ + if (cliptype > static_cast(ClipType::Xor)) return -4; + if (fillrule > static_cast(FillRule::Negative)) return -3; + + Paths64 sub, sub_open, clp, sol, sol_open; + sub = ConvertCPaths64(subjects); + sub_open = ConvertCPaths64(subjects_open); + clp = ConvertCPaths64(clips); + + Clipper64 clipper; + clipper.PreserveCollinear = preserve_collinear; + clipper.ReverseSolution = reverse_solution; + if (sub.size() > 0) clipper.AddSubject(sub); + if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); + if (clp.size() > 0) clipper.AddClip(clp); + if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open)) + return -1; // clipping bug - should never happen :) + solution = CreateCPaths64(sol); + solution_open = CreateCPaths64(sol_open); + return 0; //success !! +} + +EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype, + uint8_t fillrule, const CPaths64 subjects, + const CPaths64 subjects_open, const CPaths64 clips, + CPolyTree64*& solution, CPaths64& solution_open, + bool preserve_collinear = true, bool reverse_solution = false) +{ + if (cliptype > static_cast(ClipType::Xor)) return -4; + if (fillrule > static_cast(FillRule::Negative)) return -3; + Paths64 sub, sub_open, clp, sol_open; + sub = ConvertCPaths64(subjects); + sub_open = ConvertCPaths64(subjects_open); + clp = ConvertCPaths64(clips); + + PolyTree64 pt; + Clipper64 clipper; + clipper.PreserveCollinear = preserve_collinear; + clipper.ReverseSolution = reverse_solution; + if (sub.size() > 0) clipper.AddSubject(sub); + if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); + if (clp.size() > 0) clipper.AddClip(clp); + if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), pt, sol_open)) + return -1; // clipping bug - should never happen :) + + solution = CreateCPolyTree64(pt); + solution_open = CreateCPaths64(sol_open); + return 0; //success !! +} + +EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, + uint8_t fillrule, const CPathsD subjects, + const CPathsD subjects_open, const CPathsD clips, + CPathsD& solution, CPathsD& solution_open, int precision = 2, + bool preserve_collinear = true, bool reverse_solution = false) +{ + if (precision < -8 || precision > 8) return -5; + if (cliptype > static_cast(ClipType::Xor)) return -4; + if (fillrule > static_cast(FillRule::Negative)) return -3; + const double scale = std::pow(10, precision); + + Paths64 sub, sub_open, clp, sol, sol_open; + sub = ConvertCPathsD(subjects, scale); + sub_open = ConvertCPathsD(subjects_open, scale); + clp = ConvertCPathsD(clips, scale); + + Clipper64 clipper; + clipper.PreserveCollinear = preserve_collinear; + clipper.ReverseSolution = reverse_solution; + if (sub.size() > 0) clipper.AddSubject(sub); + if (sub_open.size() > 0) + clipper.AddOpenSubject(sub_open); + if (clp.size() > 0) clipper.AddClip(clp); + if (!clipper.Execute(ClipType(cliptype), + FillRule(fillrule), sol, sol_open)) return -1; + + if (sol.size() > 0) solution = CreateCPathsD(sol, 1 / scale); + if (sol_open.size() > 0) + solution_open = CreateCPathsD(sol_open, 1 / scale); + return 0; +} + +EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype, + uint8_t fillrule, const CPathsD subjects, + const CPathsD subjects_open, const CPathsD clips, + CPolyTreeD*& solution, CPathsD& solution_open, int precision = 2, + bool preserve_collinear = true, bool reverse_solution = false) +{ + if (precision < -8 || precision > 8) return -5; + if (cliptype > static_cast(ClipType::Xor)) return -4; + if (fillrule > static_cast(FillRule::Negative)) return -3; + + const double scale = std::pow(10, precision); + Paths64 sub, sub_open, clp, sol_open; + sub = ConvertCPathsD(subjects, scale); + sub_open = ConvertCPathsD(subjects_open, scale); + clp = ConvertCPathsD(clips, scale); + + PolyTree64 sol; + Clipper64 clipper; + clipper.PreserveCollinear = preserve_collinear; + clipper.ReverseSolution = reverse_solution; + if (sub.size() > 0) clipper.AddSubject(sub); + if (sub_open.size() > 0) + clipper.AddOpenSubject(sub_open); + if (clp.size() > 0) clipper.AddClip(clp); + if (!clipper.Execute(ClipType(cliptype), + FillRule(fillrule), sol, sol_open)) return -1; + + solution = CreateCPolyTreeD(sol, 1 / scale); + if (sol_open.size() > 0) + solution_open = CreateCPathsD(sol_open, 1 / scale); + return 0; +} + +EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths, + double delta, uint8_t jt, uint8_t et, double miter_limit = 2.0, + double arc_tolerance = 0.0, bool reverse_solution = false) +{ + Paths64 pp; + pp = ConvertCPaths64(paths); + + ClipperOffset clip_offset( miter_limit, + arc_tolerance, reverse_solution); + clip_offset.AddPaths(pp, JoinType(jt), EndType(et)); + Paths64 result = clip_offset.Execute(delta); + return CreateCPaths64(result); +} + +EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, + double delta, uint8_t jt, uint8_t et, + double precision = 2, double miter_limit = 2.0, + double arc_tolerance = 0.0, bool reverse_solution = false) +{ + 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 = ConvertCPathsD(paths, scale); + clip_offset.AddPaths(pp, JoinType(jt), EndType(et)); + Paths64 result = clip_offset.Execute(delta * scale); + return CreateCPathsD(result, 1/scale); +} + +EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, + const CPaths64 paths) +{ + log(rect.left); + log(rect.right); + if (CRectIsEmpty(rect) || !paths) return nullptr; + Rect64 r64 = CRectToRect(rect); + class RectClip rc(r64); + Paths64 pp = ConvertCPaths64(paths); + Paths64 result; + result.reserve(pp.size()); + for (const Path64& p : pp) + { + Rect64 pathRec = Bounds(p); + if (!r64.Intersects(pathRec)) continue; + + if (r64.Contains(pathRec)) + result.push_back(p); + else + { + Path64 p2 = rc.Execute(p); + if (!p2.empty()) result.push_back(std::move(p2)); + } + } + return CreateCPaths64(result); +} + +EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, + const CPathsD paths, int precision = 2) +{ + 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); + Paths64 pp = ConvertCPathsD(paths, scale); + class RectClip rc(r); + Paths64 result; + result.reserve(pp.size()); + for (const Path64& p : pp) + { + Rect64 pathRec = Bounds(p); + if (!r.Intersects(pathRec)) continue; + + if (r.Contains(pathRec)) + result.push_back(p); + else + { + Path64 p2 = rc.Execute(p); + if (!p2.empty()) result.push_back(std::move(p2)); + } + } + return CreateCPathsD(result, 1/scale); +} + +EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, + const CPaths64 paths) +{ + if (CRectIsEmpty(rect) || !paths) return nullptr; + Rect64 r = CRectToRect(rect); + class RectClipLines rcl (r); + Paths64 pp = ConvertCPaths64(paths); + Paths64 result; + result.reserve(pp.size()); + + for (const Path64& p : pp) + { + Rect64 pathRec = Bounds(p); + if (!r.Intersects(pathRec)) continue; + + if (r.Contains(pathRec)) + result.push_back(p); + else + { + Paths64 pp2 = rcl.Execute(p); + if (!pp2.empty()) + result.insert(result.end(), pp2.begin(), pp2.end()); + } + } + return CreateCPaths64(result); +} + +EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, + const CPathsD paths, int precision = 2) +{ + Paths64 result; + 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 RectClipLines rcl(r); + Paths64 pp = ConvertCPathsD(paths, scale); + + result.reserve(pp.size()); + for (const Path64& p : pp) + { + Rect64 pathRec = Bounds(p); + if (!r.Intersects(pathRec)) continue; + + if (r.Contains(pathRec)) + result.push_back(p); + else + { + Paths64 pp2 = rcl.Execute(p); + if (pp2.empty()) continue; + result.insert(result.end(), pp2.begin(), pp2.end()); + } + } + return CreateCPathsD(result, 1/scale); +} + +inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2) +{ + // create a dummy counter path + CPath64 result = new int64_t[2 + cnt1 *2]; + result[0] = cnt1; + result[1] = cnt2; + return result; +} + +inline CPath64 CreateCPath64(const Path64& p) +{ + size_t cnt = p.size(); + if (!cnt) return nullptr; + CPath64 result = CreateCPath64(cnt, 0); + CPath64 v = result; + v += 2; // skip counters + for (const Point64& pt : p) + { + *v++ = pt.x; + *v++ = pt.y; + } + return result; +} + +inline Path64 ConvertCPath64(const CPath64& p) +{ + Path64 result; + if (p && *p) + { + CPath64 v = p; + const size_t cnt = static_cast(p[0]); + v += 2; // skip counters + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + { + // x,y here avoids right to left function evaluation + // result.push_back(Point64(*v++, *v++)); + int64_t x = *v++; + int64_t y = *v++; + result.push_back(Point64(x, y)); + } + } + return result; +} + +inline CPaths64 CreateCPaths64(const Paths64& pp) +{ + size_t cnt = pp.size(), cnt2 = cnt; + + // don't allocate space for empty paths + for (size_t i = 0; i < cnt; ++i) + if (!pp[i].size()) --cnt2; + if (!cnt2) return nullptr; + + CPaths64 result = new int64_t* [cnt2 + 1]; + CPaths64 v = result; + *v++ = CreateCPath64(0, cnt2); // assign a counter path + for (const Path64& p : pp) + { + *v = CreateCPath64(p); + if (*v) ++v; + } + return result; +} + +inline Paths64 ConvertCPaths64(const CPaths64& pp) +{ + Paths64 result; + if (pp) + { + CPaths64 v = pp; + CPath64 cnts = pp[0]; + const size_t cnt = static_cast(cnts[1]); // nb 2nd cnt + ++v; // skip cnts + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + result.push_back(ConvertCPath64(*v++)); + } + return result; +} + +inline CPathD CreateCPathD(size_t cnt1, size_t cnt2) +{ + // create a dummy path counter + CPathD result = new double[2 + cnt1 * 2]; + result[0] = static_cast(cnt1); + result[1] = static_cast(cnt2); + return result; +} + +inline CPathD CreateCPathD(const PathD& p) +{ + size_t cnt = p.size(); + if (!cnt) return nullptr; + CPathD result = CreateCPathD(cnt, 0); + CPathD v = result; + v += 2; // skip counters + for (const PointD& pt : p) + { + *v++ = pt.x; + *v++ = pt.y; + } + return result; +} + +inline PathD ConvertCPathD(const CPathD& p) +{ + PathD result; + if (p) + { + CPathD v = p; + size_t cnt = static_cast(v[0]); + v += 2; // skip counters + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + { + // x,y here avoids right to left function evaluation + // result.push_back(PointD(*v++, *v++)); + double x = *v++; + double y = *v++; + result.push_back(PointD(x, y)); + } + } + return result; +} + +inline CPathsD CreateCPathsD(const PathsD& pp) +{ + size_t cnt = pp.size(), cnt2 = cnt; + // don't allocate space for empty paths + for (size_t i = 0; i < cnt; ++i) + if (!pp[i].size()) --cnt2; + if (!cnt2) return nullptr; + CPathsD result = new double * [cnt2 + 1]; + CPathsD v = result; + *v++ = CreateCPathD(0, cnt2); // assign counter path + for (const PathD& p : pp) + { + *v = CreateCPathD(p); + if (*v) { ++v; } + } + return result; +} + +inline PathsD ConvertCPathsD(const CPathsD& pp) +{ + PathsD result; + if (pp) + { + CPathsD v = pp; + CPathD cnts = v[0]; + size_t cnt = static_cast(cnts[1]); + ++v; // skip cnts path + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + result.push_back(ConvertCPathD(*v++)); + } + return result; +} + +inline Path64 ConvertCPathD(const CPathD& p, double scale) +{ + Path64 result; + if (p) + { + CPathD v = p; + size_t cnt = static_cast(*v); + v += 2; // skip counters + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + { + // x,y here avoids right to left function evaluation + // result.push_back(PointD(*v++, *v++)); + double x = *v++ * scale; + double y = *v++ * scale; + result.push_back(Point64(x, y)); + } + } + return result; +} + +inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale) +{ + Paths64 result; + if (pp) + { + CPathsD v = pp; + CPathD cnts = v[0]; + size_t cnt = static_cast(cnts[1]); + result.reserve(cnt); + ++v; // skip cnts path + for (size_t i = 0; i < cnt; ++i) + result.push_back(ConvertCPathD(*v++, scale)); + } + return result; +} + +inline CPathD CreateCPathD(const Path64& p, double scale) +{ + size_t cnt = p.size(); + if (!cnt) return nullptr; + CPathD result = CreateCPathD(cnt, 0); + CPathD v = result; + v += 2; // skip cnts + for (const Point64& pt : p) + { + *v++ = pt.x * scale; + *v++ = pt.y * scale; + } + return result; +} + +inline CPathsD CreateCPathsD(const Paths64& pp, double scale) +{ + size_t cnt = pp.size(), cnt2 = cnt; + // don't allocate space for empty paths + for (size_t i = 0; i < cnt; ++i) + if (!pp[i].size()) --cnt2; + if (!cnt2) return nullptr; + CPathsD result = new double* [cnt2 + 1]; + CPathsD v = result; + *v++ = CreateCPathD(0, cnt2); + for (const Path64& p : pp) + { + *v = CreateCPathD(p, scale); + if (*v) ++v; + } + return result; +} + +inline void InitCPolyPath64(CPolyTree64* cpt, + bool is_hole, const PolyPath64* pp) +{ + cpt->polygon = CreateCPath64(pp->Polygon()); + cpt->is_hole = is_hole; + size_t child_cnt = pp->Count(); + cpt->child_count = child_cnt; + cpt->childs = nullptr; + if (!child_cnt) return; + cpt->childs = new CPolyPath64[child_cnt]; + CPolyPath64* child = cpt->childs; + for (const PolyPath64* pp_child : *pp) + InitCPolyPath64(child++, !is_hole, pp_child); +} + +inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt) +{ + CPolyTree64* result = new CPolyTree64(); + result->polygon = nullptr; + result->is_hole = false; + size_t child_cnt = pt.Count(); + result->childs = nullptr; + result->child_count = child_cnt; + if (!child_cnt) return result; + result->childs = new CPolyPath64[child_cnt]; + CPolyPath64* child = result->childs; + for (const PolyPath64* pp : pt) + InitCPolyPath64(child++, true, pp); + return result; +} + +inline void DisposeCPolyPath64(CPolyPath64* cpp) +{ + if (!cpp->child_count) return; + CPolyPath64* child = cpp->childs; + for (size_t i = 0; i < cpp->child_count; ++i) + DisposeCPolyPath64(child); + delete[] cpp->childs; +} + +EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64*& cpt) +{ + if (!cpt) return; + DisposeCPolyPath64(cpt); + delete cpt; + cpt = nullptr; +} + +inline void InitCPolyPathD(CPolyTreeD* cpt, + bool is_hole, const PolyPath64* pp, double scale) +{ + cpt->polygon = CreateCPathD(pp->Polygon(), scale); + cpt->is_hole = is_hole; + size_t child_cnt = pp->Count(); + cpt->child_count = child_cnt; + cpt->childs = nullptr; + if (!child_cnt) return; + cpt->childs = new CPolyPathD[child_cnt]; + CPolyPathD* child = cpt->childs; + for (const PolyPath64* pp_child : *pp) + InitCPolyPathD(child++, !is_hole, pp_child, scale); +} + +inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale) +{ + CPolyTreeD* result = new CPolyTreeD(); + result->polygon = nullptr; + result->is_hole = false; + size_t child_cnt = pt.Count(); + result->child_count = static_cast(child_cnt); + result->childs = nullptr; + if (!child_cnt) return result; + result->childs = new CPolyPathD[child_cnt]; + CPolyPathD* child = result->childs; + for (const PolyPath64* pp : pt) + InitCPolyPathD(child++, true, pp, scale); + return result; +} + +inline void DisposeCPolyPathD(CPolyPathD* cpp) +{ + if (!cpp->child_count) return; + CPolyPathD* child = cpp->childs; + for (size_t i = 0; i < cpp->child_count; ++i) + DisposeCPolyPathD(child++); + delete[] cpp->childs; +} + +EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD*& cpt) +{ + if (!cpt) return; + DisposeCPolyPathD(cpt); + delete cpt; + cpt = nullptr; +} + +} // end Clipper2Lib namespace + +#endif // CLIPPER2_EXPORT_H diff --git a/thirdparty/clipper2/clipper.h b/thirdparty/clipper2/clipper.h index 8ab191ae8c..13929cdcaf 100644 --- a/thirdparty/clipper2/clipper.h +++ b/thirdparty/clipper2/clipper.h @@ -1,7 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Version : Clipper2 - ver.1.0.5 * -* Date : 2 October 2022 * +* Date : 26 October 2022 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2022 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -18,9 +17,12 @@ #include "clipper.engine.h" #include "clipper.offset.h" #include "clipper.minkowski.h" +#include "clipper.rectclip.h" -namespace Clipper2Lib -{ +namespace Clipper2Lib { + + static const char* precision_error = + "Precision exceeds the permitted range"; static const Rect64 MaxInvalidRect64 = Rect64( (std::numeric_limits::max)(), @@ -36,7 +38,7 @@ namespace Clipper2Lib inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule, const Paths64& subjects, const Paths64& clips) - { + { Paths64 result; Clipper64 clipper; clipper.AddSubject(subjects); @@ -59,7 +61,7 @@ namespace Clipper2Lib const PathsD& subjects, const PathsD& clips, int decimal_prec = 2) { if (decimal_prec > 8 || decimal_prec < -8) - throw Clipper2Exception("invalid decimal precision"); + throw Clipper2Exception(precision_error); PathsD result; ClipperD clipper(decimal_prec); clipper.AddSubject(subjects); @@ -68,6 +70,19 @@ namespace Clipper2Lib return result; } + inline void BooleanOp(ClipType cliptype, FillRule fillrule, + const PathsD& subjects, const PathsD& clips, + PolyTreeD& polytree, int decimal_prec = 2) + { + if (decimal_prec > 8 || decimal_prec < -8) + throw Clipper2Exception(precision_error); + PathsD result; + ClipperD clipper(decimal_prec); + clipper.AddSubject(subjects); + clipper.AddClip(clips); + clipper.Execute(cliptype, fillrule, polytree); + } + inline Paths64 Intersect(const Paths64& subjects, const Paths64& clips, FillRule fillrule) { return BooleanOp(ClipType::Intersection, fillrule, subjects, clips); @@ -100,7 +115,7 @@ namespace Clipper2Lib inline PathsD Union(const PathsD& subjects, FillRule fillrule, int decimal_prec = 2) { if (decimal_prec > 8 || decimal_prec < -8) - throw Clipper2Exception("invalid decimal precision"); + throw Clipper2Exception(precision_error); PathsD result; ClipperD clipper(decimal_prec); clipper.AddSubject(subjects); @@ -128,11 +143,6 @@ namespace Clipper2Lib return BooleanOp(ClipType::Xor, fillrule, subjects, clips, decimal_prec); } - inline bool IsFullOpenEndType(EndType et) - { - return (et != EndType::Polygon) && (et != EndType::Joined); - } - inline Paths64 InflatePaths(const Paths64& paths, double delta, JoinType jt, EndType et, double miter_limit = 2.0) { @@ -145,7 +155,7 @@ namespace Clipper2Lib JoinType jt, EndType et, double miter_limit = 2.0, double precision = 2) { if (precision < -8 || precision > 8) - throw new Clipper2Exception("Error: Precision exceeds the allowed range."); + throw Clipper2Exception(precision_error); const double scale = std::pow(10, precision); ClipperOffset clip_offset(miter_limit); clip_offset.AddPaths(ScalePaths(paths, scale), jt, et); @@ -247,25 +257,185 @@ namespace Clipper2Lib return rec; } + inline Path64 RectClip(const Rect64& rect, const Path64& path) + { + if (rect.IsEmpty() || path.empty()) return Path64(); + Rect64 pathRec = Bounds(path); + if (!rect.Intersects(pathRec)) return Path64(); + if (rect.Contains(pathRec)) return path; + class RectClip rc(rect); + return rc.Execute(path); + } + + inline Paths64 RectClip(const Rect64& rect, const Paths64& paths) + { + if (rect.IsEmpty() || paths.empty()) return Paths64(); + class RectClip rc(rect); + Paths64 result; + result.reserve(paths.size()); + + for (const Path64& p : paths) + { + Rect64 pathRec = Bounds(p); + if (!rect.Intersects(pathRec)) + continue; + else if (rect.Contains(pathRec)) + result.push_back(p); + else + { + Path64 p2 = rc.Execute(p); + if (!p2.empty()) result.push_back(std::move(p2)); + } + } + return result; + } + + inline PathD RectClip(const RectD& rect, const PathD& path, int precision = 2) + { + if (rect.IsEmpty() || path.empty() || + !rect.Contains(Bounds(path))) return PathD(); + if (precision < -8 || precision > 8) + throw Clipper2Exception(precision_error); + const double scale = std::pow(10, precision); + Rect64 r = ScaleRect(rect, scale); + class RectClip rc(r); + Path64 p = ScalePath(path, scale); + return ScalePath(rc.Execute(p), 1 / scale); + } + + inline PathsD RectClip(const RectD& rect, const PathsD& paths, int precision = 2) + { + if (rect.IsEmpty() || paths.empty()) return PathsD(); + if (precision < -8 || precision > 8) + throw Clipper2Exception(precision_error); + const double scale = std::pow(10, precision); + Rect64 r = ScaleRect(rect, scale); + class RectClip rc(r); + PathsD result; + result.reserve(paths.size()); + for (const PathD& path : paths) + { + RectD pathRec = Bounds(path); + if (!rect.Intersects(pathRec)) + continue; + else if (rect.Contains(pathRec)) + result.push_back(path); + else + { + Path64 p = ScalePath(path, scale); + p = rc.Execute(p); + if (!p.empty()) + result.push_back(ScalePath(p, 1 / scale)); + } + } + return result; + } + + inline Paths64 RectClipLines(const Rect64& rect, const Path64& path) + { + Paths64 result; + if (rect.IsEmpty() || path.empty()) return result; + Rect64 pathRec = Bounds(path); + if (!rect.Intersects(pathRec)) return result; + if (rect.Contains(pathRec)) + { + result.push_back(path); + return result; + } + class RectClipLines rcl(rect); + return rcl.Execute(path); + } + + inline Paths64 RectClipLines(const Rect64& rect, const Paths64& paths) + { + Paths64 result; + if (rect.IsEmpty() || paths.empty()) return result; + class RectClipLines rcl(rect); + for (const Path64& p : paths) + { + Rect64 pathRec = Bounds(p); + if (!rect.Intersects(pathRec)) + continue; + else if (rect.Contains(pathRec)) + result.push_back(p); + else + { + Paths64 pp = rcl.Execute(p); + if (!pp.empty()) + result.insert(result.end(), pp.begin(), pp.end()); + } + } + return result; + } + + inline PathsD RectClipLines(const RectD& rect, const PathD& path, int precision = 2) + { + if (rect.IsEmpty() || path.empty() || + !rect.Contains(Bounds(path))) return PathsD(); + if (precision < -8 || precision > 8) + throw Clipper2Exception(precision_error); + const double scale = std::pow(10, precision); + Rect64 r = ScaleRect(rect, scale); + class RectClipLines rcl(r); + Path64 p = ScalePath(path, scale); + return ScalePaths(rcl.Execute(p), 1 / scale); + } + + inline PathsD RectClipLines(const RectD& rect, const PathsD& paths, int precision = 2) + { + PathsD result; + if (rect.IsEmpty() || paths.empty()) return result; + if (precision < -8 || precision > 8) + throw Clipper2Exception(precision_error); + const double scale = std::pow(10, precision); + Rect64 r = ScaleRect(rect, scale); + class RectClipLines rcl(r); + result.reserve(paths.size()); + for (const PathD& path : paths) + { + RectD pathRec = Bounds(path); + if (!rect.Intersects(pathRec)) + continue; + else if (rect.Contains(pathRec)) + result.push_back(path); + else + { + Path64 p = ScalePath(path, scale); + Paths64 pp = rcl.Execute(p); + if (pp.empty()) continue; + PathsD ppd = ScalePaths(pp, 1 / scale); + result.insert(result.end(), ppd.begin(), ppd.end()); + } + } + return result; + } + namespace details { - template - inline void InternalPolyNodeToPaths(const PolyPath& polypath, Paths& paths) + inline void PolyPathToPaths64(const PolyPath64& polypath, Paths64& paths) { paths.push_back(polypath.Polygon()); - for (auto child : polypath) - InternalPolyNodeToPaths(*child, paths); + for (const PolyPath* child : polypath) + PolyPathToPaths64(*(PolyPath64*)(child), paths); } - inline bool InternalPolyPathContainsChildren(const PolyPath64& pp) + inline void PolyPathToPathsD(const PolyPathD& polypath, PathsD& paths) { - for (auto child : pp) + paths.push_back(polypath.Polygon()); + for (const PolyPath* child : polypath) + PolyPathToPathsD(*(PolyPathD*)(child), paths); + } + + inline bool PolyPath64ContainsChildren(const PolyPath64& pp) + { + for (auto ch : pp) { + PolyPath64* child = (PolyPath64*)ch; for (const Point64& pt : child->Polygon()) if (PointInPolygon(pt, pp.Polygon()) == PointInPolygonResult::IsOutside) return false; - if (child->Count() > 0 && !InternalPolyPathContainsChildren(*child)) + if (child->Count() > 0 && !PolyPath64ContainsChildren(*child)) return false; } return true; @@ -361,20 +531,28 @@ namespace Clipper2Lib } // end details namespace - template - inline Paths PolyTreeToPaths(const PolyTree& polytree) + inline Paths64 PolyTreeToPaths64(const PolyTree64& polytree) { - Paths result; + Paths64 result; for (auto child : polytree) - details::InternalPolyNodeToPaths(*child, result); + details::PolyPathToPaths64(*(PolyPath64*)(child), result); + return result; + } + + inline PathsD PolyTreeToPathsD(const PolyTreeD& polytree) + { + PathsD result; + for (auto child : polytree) + details::PolyPathToPathsD(*(PolyPathD*)(child), result); return result; } inline bool CheckPolytreeFullyContainsChildren(const PolyTree64& polytree) { for (auto child : polytree) - if (child->Count() > 0 && !details::InternalPolyPathContainsChildren(*child)) - return false; + if (child->Count() > 0 && + !details::PolyPath64ContainsChildren(*(PolyPath64*)(child))) + return false; return true; } @@ -464,7 +642,7 @@ namespace Clipper2Lib inline PathD TrimCollinear(const PathD& path, int precision, bool is_open_path = false) { if (precision > 8 || precision < -8) - throw new Clipper2Exception("Error: Precision exceeds the allowed range."); + throw Clipper2Exception(precision_error); const double scale = std::pow(10, precision); Path64 p = ScalePath(path, scale); p = TrimCollinear(p, is_open_path); diff --git a/thirdparty/clipper2/clipper.minkowski.h b/thirdparty/clipper2/clipper.minkowski.h index 99a586b1e0..ca0ab6be81 100644 --- a/thirdparty/clipper2/clipper.minkowski.h +++ b/thirdparty/clipper2/clipper.minkowski.h @@ -1,7 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Version : Clipper2 - ver.1.0.0 * -* Date : 3 August 2022 * +* Date : 15 October 2022 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2022 * * Purpose : Minkowski Sum and Difference * @@ -31,21 +30,21 @@ namespace Clipper2Lib if (isSum) { - for (Point64 pt : path) + for (const Point64& p : path) { Path64 path2(pattern.size()); std::transform(pattern.cbegin(), pattern.cend(), - path2.begin(), [pt](const Point64& pt2) {return pt + pt2; }); + path2.begin(), [p](const Point64& pt2) {return p + pt2; }); tmp.push_back(path2); } } else { - for (Point64 pt : path) + for (const Point64& p : path) { Path64 path2(pattern.size()); std::transform(pattern.cbegin(), pattern.cend(), - path2.begin(), [pt](const Point64& pt2) {return pt - pt2; }); + path2.begin(), [p](const Point64& pt2) {return p - pt2; }); tmp.push_back(path2); } } diff --git a/thirdparty/clipper2/clipper.offset.cpp b/thirdparty/clipper2/clipper.offset.cpp index 46ada51169..fdda0725f1 100644 --- a/thirdparty/clipper2/clipper.offset.cpp +++ b/thirdparty/clipper2/clipper.offset.cpp @@ -1,7 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Version : Clipper2 - ver.1.0.5 * -* Date : 2 October 2022 * +* Date : 15 October 2022 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2022 * * Purpose : Path Offset (Inflate/Shrink) * @@ -271,15 +270,18 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k) { if (join_type_ == JoinType::Round) DoRound(group, path, j, k, std::atan2(sin_a, cos_a)); - // else miter when the angle isn't too acute (and hence exceed ML) - else if (join_type_ == JoinType::Miter && cos_a > temp_lim_ - 1) - DoMiter(group, path, j, k, cos_a); - // else only square angles that deviate > 90 degrees - else if (cos_a < -0.001) - DoSquare(group, path, j, k); + 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); + } + // don't bother squaring angles that deviate < ~20 degrees because + // squaring will be indistinguishable from mitering and just be a lot slower + else if (cos_a > 0.9) + DoMiter(group, path, j, k, cos_a); else - // don't square shallow angles that are safe to miter - DoMiter(group, path, j, k, cos_a); + DoSquare(group, path, j, k); } k = j; } diff --git a/thirdparty/clipper2/clipper.offset.h b/thirdparty/clipper2/clipper.offset.h index 0c9d824208..4fd130bf4d 100644 --- a/thirdparty/clipper2/clipper.offset.h +++ b/thirdparty/clipper2/clipper.offset.h @@ -1,7 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Version : Clipper2 - ver.1.0.4 * -* Date : 14 August 2022 * +* Date : 15 October 2022 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2022 * * Purpose : Path Offset (Inflate/Shrink) * diff --git a/thirdparty/clipper2/clipper.rectclip.cpp b/thirdparty/clipper2/clipper.rectclip.cpp new file mode 100644 index 0000000000..762826fb96 --- /dev/null +++ b/thirdparty/clipper2/clipper.rectclip.cpp @@ -0,0 +1,547 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 26 October 2022 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2022 * +* Purpose : FAST rectangular clipping * +* License : http://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#include +#include "clipper.h" +#include "clipper.rectclip.h" + +namespace Clipper2Lib { + + //------------------------------------------------------------------------------ + // Miscellaneous methods + //------------------------------------------------------------------------------ + + inline PointInPolygonResult Path1ContainsPath2(Path64 path1, Path64 path2) + { + PointInPolygonResult result = PointInPolygonResult::IsOn; + for(const Point64& pt : path2) + { + result = PointInPolygon(pt, path1); + if (result != PointInPolygonResult::IsOn) break; + } + return result; + } + + inline bool GetLocation(const Rect64& rec, + const Point64& pt, Location& loc) + { + if (pt.x == rec.left && pt.y >= rec.top && pt.y <= rec.bottom) + { + loc = Location::Left; + return false; + } + else if (pt.x == rec.right && pt.y >= rec.top && pt.y <= rec.bottom) + { + loc = Location::Right; + return false; + } + else if (pt.y == rec.top && pt.x >= rec.left && pt.x <= rec.right) + { + loc = Location::Top; + return false; + } + else if (pt.y == rec.bottom && pt.x >= rec.left && pt.x <= rec.right) + { + loc = Location::Bottom; + return false; + } + else if (pt.x < rec.left) loc = Location::Left; + else if (pt.x > rec.right) loc = Location::Right; + else if (pt.y < rec.top) loc = Location::Top; + else if (pt.y > rec.bottom) loc = Location::Bottom; + else loc = Location::Inside; + return true; + } + + Point64 GetIntersectPoint64(const Point64& ln1a, const Point64& ln1b, + const Point64& ln2a, const Point64& ln2b) + { + // see http://astronomy.swin.edu.au/~pbourke/geometry/lineline2d/ + if (ln1b.x == ln1a.x) + { + if (ln2b.x == ln2a.x) return Point64(); // parallel lines + double m2 = static_cast(ln2b.y - ln2a.y) / (ln2b.x - ln2a.x); + double b2 = ln2a.y - m2 * ln2a.x; + return Point64(ln1a.x, static_cast(std::round(m2 * ln1a.x + b2))); + } + else if (ln2b.x == ln2a.x) + { + double m1 = static_cast(ln1b.y - ln1a.y) / (ln1b.x - ln1a.x); + double b1 = ln1a.y - m1 * ln1a.x; + return Point64(ln2a.x, static_cast(std::round(m1 * ln2a.x + b1))); + } + else + { + double m1 = static_cast(ln1b.y - ln1a.y) / (ln1b.x - ln1a.x); + double b1 = ln1a.y - m1 * ln1a.x; + double m2 = static_cast(ln2b.y - ln2a.y) / (ln2b.x - ln2a.x); + double b2 = ln2a.y - m2 * ln2a.x; + if (std::fabs(m1 - m2) > 1.0E-15) + { + double x = (b2 - b1) / (m1 - m2); + return Point64(x, m1 * x + b1); + } + else + return Point64((ln1a.x + ln1b.x) * 0.5, (ln1a.y + ln1b.y) * 0.5); + } + } + + inline bool GetIntersection(const Path64& rectPath, + const Point64& p, const Point64& p2, Location& loc, Point64& ip) + { + // gets the intersection closest to 'p' + // when Result = false, loc will remain unchanged + switch (loc) + { + case Location::Left: + if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]); + else if (p.y < rectPath[0].y && + SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + { + ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]); + loc = Location::Top; + } + else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + { + ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]); + loc = Location::Bottom; + } + else return false; + break; + + case Location::Top: + if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]); + else if (p.x < rectPath[0].x && + SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + { + ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]); + loc = Location::Left; + } + else if (p.x > rectPath[1].x && + SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + { + ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]); + loc = Location::Right; + } + else return false; + break; + + case Location::Right: + if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]); + else if (p.y < rectPath[0].y && + SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + { + ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]); + loc = Location::Top; + } + else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + { + ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]); + loc = Location::Bottom; + } + else return false; + break; + + case Location::Bottom: + if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]); + else if (p.x < rectPath[3].x && + SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + { + ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]); + loc = Location::Left; + } + else if (p.x > rectPath[2].x && + SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + { + ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]); + loc = Location::Right; + } + else return false; + break; + + default: // loc == rInside + if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + { + ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]); + loc = Location::Left; + } + else if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + { + ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]); + loc = Location::Top; + } + else if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + { + ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]); + loc = Location::Right; + } + else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + { + ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]); + loc = Location::Bottom; + } + else return false; + break; + } + + return true; + } + + inline Location GetAdjacentLocation(Location loc, bool isClockwise) + { + int delta = (isClockwise) ? 1 : 3; + return static_cast((static_cast(loc) + delta) % 4); + } + + inline bool HeadingClockwise(Location prev, Location curr) + { + return (static_cast(prev) + 1) % 4 == static_cast(curr); + } + + inline bool AreOpposites(Location prev, Location curr) + { + return abs(static_cast(prev) - static_cast(curr)) == 2; + } + + inline bool IsClockwise(Location prev, Location curr, + Point64 prev_pt, Point64 curr_pt, Point64 rect_mp) + { + if (AreOpposites(prev, curr)) + return CrossProduct(prev_pt, rect_mp, curr_pt) < 0; + else + return HeadingClockwise(prev, curr); + } + + //---------------------------------------------------------------------------- + // RectClip64 + //---------------------------------------------------------------------------- + + void RectClip::AddCorner(Location prev, Location curr) + { + if (HeadingClockwise(prev, curr)) + result_.push_back(rectPath_[static_cast(prev)]); + else + result_.push_back(rectPath_[static_cast(curr)]); + } + + void RectClip::AddCorner(Location& loc, bool isClockwise) + { + if (isClockwise) + { + result_.push_back(rectPath_[static_cast(loc)]); + loc = GetAdjacentLocation(loc, true); + } + else + { + loc = GetAdjacentLocation(loc, false); + result_.push_back(rectPath_[static_cast(loc)]); + } + } + + void RectClip::GetNextLocation(const Path64& path, + Location& loc, int& i, int highI) + { + switch (loc) + { + case Location::Left: + while (i <= highI && path[i].x <= rect_.left) ++i; + if (i > highI) break; + else if (path[i].x >= rect_.right) loc = Location::Right; + else if (path[i].y <= rect_.top) loc = Location::Top; + else if (path[i].y >= rect_.bottom) loc = Location::Bottom; + else loc = Location::Inside; + break; + + case Location::Top: + while (i <= highI && path[i].y <= rect_.top) ++i; + if (i > highI) break; + else if (path[i].y >= rect_.bottom) loc = Location::Bottom; + else if (path[i].x <= rect_.left) loc = Location::Left; + else if (path[i].x >= rect_.right) loc = Location::Right; + else loc = Location::Inside; + break; + + case Location::Right: + while (i <= highI && path[i].x >= rect_.right) ++i; + if (i > highI) break; + else if (path[i].x <= rect_.left) loc = Location::Left; + else if (path[i].y <= rect_.top) loc = Location::Top; + else if (path[i].y >= rect_.bottom) loc = Location::Bottom; + else loc = Location::Inside; + break; + + case Location::Bottom: + while (i <= highI && path[i].y >= rect_.bottom) ++i; + if (i > highI) break; + else if (path[i].y <= rect_.top) loc = Location::Top; + else if (path[i].x <= rect_.left) loc = Location::Left; + else if (path[i].x >= rect_.right) loc = Location::Right; + else loc = Location::Inside; + break; + + case Location::Inside: + while (i <= highI) + { + if (path[i].x < rect_.left) loc = Location::Left; + else if (path[i].x > rect_.right) loc = Location::Right; + else if (path[i].y > rect_.bottom) loc = Location::Bottom; + else if (path[i].y < rect_.top) loc = Location::Top; + else { result_.push_back(path[i]); ++i; continue; } + break; //inner loop + } + break; + } //switch + } + + Path64 RectClip::Execute(const Path64& path) + { + if (rect_.IsEmpty() || path.size() < 3) return Path64(); + + result_.clear(); + start_locs_.clear(); + int i = 0, highI = static_cast(path.size()) - 1; + Location prev = Location::Inside, loc; + Location crossing_loc = Location::Inside; + Location first_cross_ = Location::Inside; + if (!GetLocation(rect_, path[highI], loc)) + { + i = highI - 1; + while (i >= 0 && !GetLocation(rect_, path[i], prev)) --i; + if (i < 0) return path; + if (prev == Location::Inside) loc = Location::Inside; + i = 0; + } + Location starting_loc = loc; + + /////////////////////////////////////////////////// + while (i <= highI) + { + prev = loc; + Location crossing_prev = crossing_loc; + + GetNextLocation(path, loc, i, highI); + + if (i > highI) break; + Point64 ip, ip2; + Point64 prev_pt = (i) ? path[static_cast(i - 1)] : path[highI]; + + crossing_loc = loc; + if (!GetIntersection(rectPath_, path[i], prev_pt, crossing_loc, ip)) + { + // ie remaining outside + + if (crossing_prev == Location::Inside) + { + bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], mp_); + do { + start_locs_.push_back(prev); + prev = GetAdjacentLocation(prev, isClockw); + } while (prev != loc); + crossing_loc = crossing_prev; // still not crossed + } + else if (prev != Location::Inside && prev != loc) + { + bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], mp_); + do { + AddCorner(prev, isClockw); + } while (prev != loc); + } + ++i; + continue; + } + + //////////////////////////////////////////////////// + // we must be crossing the rect boundary to get here + //////////////////////////////////////////////////// + + if (loc == Location::Inside) // path must be entering rect + { + if (first_cross_ == Location::Inside) + { + first_cross_ = crossing_loc; + start_locs_.push_back(prev); + } + else if (prev != crossing_loc) + { + bool isClockw = IsClockwise(prev, crossing_loc, prev_pt, path[i], mp_); + do { + AddCorner(prev, isClockw); + } while (prev != crossing_loc); + } + } + else if (prev != Location::Inside) + { + // passing right through rect. 'ip' here will be the second + // intersect pt but we'll also need the first intersect pt (ip2) + loc = prev; + GetIntersection(rectPath_, prev_pt, path[i], loc, ip2); + if (crossing_prev != Location::Inside) + AddCorner(crossing_prev, loc); + + if (first_cross_ == Location::Inside) + { + first_cross_ = loc; + start_locs_.push_back(prev); + } + + loc = crossing_loc; + result_.push_back(ip2); + if (ip == ip2) + { + // it's very likely that path[i] is on rect + GetLocation(rect_, path[i], loc); + AddCorner(crossing_loc, loc); + crossing_loc = loc; + continue; + } + } + else // path must be exiting rect + { + loc = crossing_loc; + if (first_cross_ == Location::Inside) + first_cross_ = crossing_loc; + } + + result_.push_back(ip); + + } //while i <= highI + /////////////////////////////////////////////////// + + if (first_cross_ == Location::Inside) + { + if (starting_loc == Location::Inside) return path; + Rect64 tmp_rect = Bounds(path); + if (tmp_rect.Contains(rect_) && + Path1ContainsPath2(path, rectPath_) != + PointInPolygonResult::IsOutside) return rectPath_; + else + return Path64(); + } + + if (loc != Location::Inside && + (loc != first_cross_ || start_locs_.size() > 2)) + { + if (start_locs_.size() > 0) + { + prev = loc; + for (auto loc2 : start_locs_) + { + if (prev == loc2) continue; + AddCorner(prev, HeadingClockwise(prev, loc2)); + prev = loc2; + } + loc = prev; + } + if (loc != first_cross_) + AddCorner(loc, HeadingClockwise(loc, first_cross_)); + } + + if (result_.size() < 3) return Path64(); + + // tidy up duplicates and collinear segments + Path64 res; + res.reserve(result_.size()); + size_t k = 0; highI = static_cast(result_.size()) - 1; + Point64 prev_pt = result_[highI]; + res.push_back(result_[0]); + Path64::const_iterator cit; + for (cit = result_.cbegin() + 1; cit != result_.cend(); ++cit) + { + if (CrossProduct(prev_pt, res[k], *cit)) + { + prev_pt = res[k++]; + res.push_back(*cit); + } + else + res[k] = *cit; + } + + if (k < 2) return Path64(); + // and a final check for collinearity + else if (!CrossProduct(res[0], res[k - 1], res[k])) res.pop_back(); + return res; + } + + Paths64 RectClipLines::Execute(const Path64& path) + { + result_.clear(); + Paths64 result; + if (rect_.IsEmpty() || path.size() == 0) return result; + + int i = 1, highI = static_cast(path.size()) - 1; + + Location prev = Location::Inside, loc; + Location crossing_loc = Location::Inside; + if (!GetLocation(rect_, path[0], loc)) + { + while (i <= highI && !GetLocation(rect_, path[i], prev)) ++i; + if (i > highI) { + result.push_back(path); + return result; + } + if (prev == Location::Inside) loc = Location::Inside; + i = 1; + } + if (loc == Location::Inside) result_.push_back(path[0]); + + /////////////////////////////////////////////////// + while (i <= highI) + { + prev = loc; + GetNextLocation(path, loc, i, highI); + if (i > highI) break; + Point64 ip, ip2; + Point64 prev_pt = path[static_cast(i - 1)]; + + crossing_loc = loc; + if (!GetIntersection(rectPath_, path[i], prev_pt, crossing_loc, ip)) + { + // ie remaining outside + ++i; + continue; + } + + //////////////////////////////////////////////////// + // we must be crossing the rect boundary to get here + //////////////////////////////////////////////////// + + if (loc == Location::Inside) // path must be entering rect + { + result_.push_back(ip); + } + else if (prev != Location::Inside) + { + // passing right through rect. 'ip' here will be the second + // intersect pt but we'll also need the first intersect pt (ip2) + crossing_loc = prev; + GetIntersection(rectPath_, prev_pt, path[i], crossing_loc, ip2); + result_.push_back(ip2); + result_.push_back(ip); + result.push_back(result_); + result_.clear(); + } + else // path must be exiting rect + { + result_.push_back(ip); + result.push_back(result_); + result_.clear(); + } + } //while i <= highI + /////////////////////////////////////////////////// + + if (result_.size() > 1) + result.push_back(result_); + return result; + } + +} // namespace diff --git a/thirdparty/clipper2/clipper.rectclip.h b/thirdparty/clipper2/clipper.rectclip.h new file mode 100644 index 0000000000..98f43e0b2d --- /dev/null +++ b/thirdparty/clipper2/clipper.rectclip.h @@ -0,0 +1,50 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 26 October 2022 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2022 * +* Purpose : FAST rectangular clipping * +* License : http://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#ifndef CLIPPER_RECTCLIP_H +#define CLIPPER_RECTCLIP_H + +#include +#include +#include "clipper.h" +#include "clipper.core.h" + +namespace Clipper2Lib +{ + + enum class Location { Left, Top, Right, Bottom, Inside }; + + class RectClip { + protected: + const Rect64 rect_; + const Point64 mp_; + const Path64 rectPath_; + Path64 result_; + std::vector start_locs_; + + void GetNextLocation(const Path64& path, + Location& loc, int& i, int highI); + void AddCorner(Location prev, Location curr); + void AddCorner(Location& loc, bool isClockwise); + public: + RectClip(const Rect64& rect) : + rect_(rect), + mp_(rect.MidPoint()), + rectPath_(rect.AsPath()) {} + Path64 Execute(const Path64& path); + }; + + class RectClipLines : public RectClip { + public: + RectClipLines(const Rect64& rect) : RectClip(rect) {}; + Paths64 Execute(const Path64& path); + }; + +} // Clipper2Lib namespace +#endif // CLIPPER_RECTCLIP_H