/******************************************************************************* * Author : Angus Johnson * * Date : 27 January 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #include #include #include #include #include #include #include "clipper.engine.h" // https://github.com/AngusJohnson/Clipper2/discussions/334 // #discussioncomment-4248602 #if defined(_MSC_VER) && ( defined(_M_AMD64) || defined(_M_X64) ) #include #include #define fmin(a,b) _mm_cvtsd_f64(_mm_min_sd(_mm_set_sd(a),_mm_set_sd(b))) #define fmax(a,b) _mm_cvtsd_f64(_mm_max_sd(_mm_set_sd(a),_mm_set_sd(b))) #define nearbyint(a) _mm_cvtsd_si64(_mm_set_sd(a)) /* Note: expression type is (int64_t) */ #endif namespace Clipper2Lib { static const Rect64 invalid_rect = Rect64( std::numeric_limits::max(), std::numeric_limits::max(), -std::numeric_limits::max(), -std::numeric_limits::max() ); // Every closed path (or polygon) is made up of a series of vertices forming // edges that alternate between going up (relative to the Y-axis) and going // down. Edges consecutively going up or consecutively going down are called // 'bounds' (ie sides if they're simple polygons). 'Local Minima' refer to // vertices where descending bounds become ascending ones. struct Scanline { int64_t y = 0; Scanline* next = nullptr; explicit Scanline(int64_t y_) : y(y_) {} }; struct HorzSegSorter { inline bool operator()(const HorzSegment& hs1, const HorzSegment& hs2) { if (!hs1.right_op || !hs2.right_op) return (hs1.right_op); return hs2.left_op->pt.x > hs1.left_op->pt.x; } }; struct LocMinSorter { inline bool operator()(const LocalMinima_ptr& locMin1, const LocalMinima_ptr& locMin2) { if (locMin2->vertex->pt.y != locMin1->vertex->pt.y) return locMin2->vertex->pt.y < locMin1->vertex->pt.y; else return locMin2->vertex->pt.x > locMin1->vertex->pt.x; } }; inline bool IsOdd(int val) { return (val & 1) ? true : false; } inline bool IsHotEdge(const Active& e) { return (e.outrec); } inline bool IsOpen(const Active& e) { return (e.local_min->is_open); } inline bool IsOpenEnd(const Vertex& v) { return (v.flags & (VertexFlags::OpenStart | VertexFlags::OpenEnd)) != VertexFlags::None; } inline bool IsOpenEnd(const Active& ae) { return IsOpenEnd(*ae.vertex_top); } inline Active* GetPrevHotEdge(const Active& e) { Active* prev = e.prev_in_ael; while (prev && (IsOpen(*prev) || !IsHotEdge(*prev))) prev = prev->prev_in_ael; 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) * * | * * +inf (180deg) <--- o ---> -inf (0deg) * *******************************************************************************/ inline double GetDx(const Point64& pt1, const Point64& pt2) { double dy = double(pt2.y - pt1.y); if (dy != 0) return double(pt2.x - pt1.x) / dy; else if (pt2.x > pt1.x) return -std::numeric_limits::max(); else return std::numeric_limits::max(); } inline int64_t TopX(const Active& ae, const int64_t currentY) { 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(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(). } inline bool IsHorizontal(const Active& e) { return (e.top.y == e.bot.y); } inline bool IsHeadingRightHorz(const Active& e) { return e.dx == -std::numeric_limits::max(); } inline bool IsHeadingLeftHorz(const Active& e) { return e.dx == std::numeric_limits::max(); } inline void SwapActives(Active*& e1, Active*& e2) { Active* e = e1; e1 = e2; e2 = e; } inline PathType GetPolyType(const Active& e) { return e.local_min->polytype; } inline bool IsSamePolyType(const Active& e1, const Active& e2) { return e1.local_min->polytype == e2.local_min->polytype; } inline void SetDx(Active& e) { e.dx = GetDx(e.bot, e.top); } inline Vertex* NextVertex(const Active& e) { if (e.wind_dx > 0) return e.vertex_top->next; else return e.vertex_top->prev; } //PrevPrevVertex: useful to get the (inverted Y-axis) top of the //alternate edge (ie left or right bound) during edge insertion. inline Vertex* PrevPrevVertex(const Active& ae) { if (ae.wind_dx > 0) return ae.vertex_top->prev->prev; else return ae.vertex_top->next->next; } inline Active* ExtractFromSEL(Active* ae) { Active* res = ae->next_in_sel; if (res) res->prev_in_sel = ae->prev_in_sel; ae->prev_in_sel->next_in_sel = res; return res; } inline void Insert1Before2InSEL(Active* ae1, Active* ae2) { ae1->prev_in_sel = ae2->prev_in_sel; if (ae1->prev_in_sel) ae1->prev_in_sel->next_in_sel = ae1; ae1->next_in_sel = ae2; ae2->prev_in_sel = ae1; } inline bool IsMaxima(const Vertex& v) { return ((v.flags & VertexFlags::LocalMax) != VertexFlags::None); } inline bool IsMaxima(const Active& e) { return IsMaxima(*e.vertex_top); } inline Vertex* GetCurrYMaximaVertex_Open(const Active& e) { Vertex* result = e.vertex_top; if (e.wind_dx > 0) while ((result->next->pt.y == result->pt.y) && ((result->flags & (VertexFlags::OpenEnd | VertexFlags::LocalMax)) == VertexFlags::None)) result = result->next; else while (result->prev->pt.y == result->pt.y && ((result->flags & (VertexFlags::OpenEnd | VertexFlags::LocalMax)) == VertexFlags::None)) result = result->prev; if (!IsMaxima(*result)) result = nullptr; // not a maxima return result; } inline Vertex* GetCurrYMaximaVertex(const Active& e) { Vertex* result = e.vertex_top; if (e.wind_dx > 0) while (result->next->pt.y == result->pt.y) result = result->next; else while (result->prev->pt.y == result->pt.y) result = result->prev; if (!IsMaxima(*result)) result = nullptr; // not a maxima return result; } Active* GetMaximaPair(const Active& e) { Active* e2; e2 = e.next_in_ael; while (e2) { if (e2->vertex_top == e.vertex_top) return e2; // Found! e2 = e2->next_in_ael; } return nullptr; } inline int PointCount(OutPt* op) { OutPt* op2 = op; int cnt = 0; do { op2 = op2->next; ++cnt; } while (op2 != op); return cnt; } inline OutPt* DuplicateOp(OutPt* op, bool insert_after) { OutPt* result = new OutPt(op->pt, op->outrec); if (insert_after) { result->next = op->next; result->next->prev = result; result->prev = op; op->next = result; } else { result->prev = op->prev; result->prev->next = result; result->next = op; op->prev = result; } return result; } inline OutPt* DisposeOutPt(OutPt* op) { OutPt* result = op->next; op->prev->next = op->next; op->next->prev = op->prev; delete op; return result; } inline void DisposeOutPts(OutRec* outrec) { OutPt* op = outrec->pts; op->prev->next = nullptr; while (op) { OutPt* tmp = op; op = op->next; delete tmp; }; outrec->pts = nullptr; } bool IntersectListSort(const IntersectNode& a, const IntersectNode& b) { //note different inequality tests ... return (a.pt.y == b.pt.y) ? (a.pt.x < b.pt.x) : (a.pt.y > b.pt.y); } inline void SetSides(OutRec& outrec, Active& start_edge, Active& end_edge) { outrec.front_edge = &start_edge; outrec.back_edge = &end_edge; } void SwapOutrecs(Active& e1, Active& e2) { OutRec* or1 = e1.outrec; OutRec* or2 = e2.outrec; if (or1 == or2) { Active* e = or1->front_edge; or1->front_edge = or1->back_edge; or1->back_edge = e; return; } if (or1) { if (&e1 == or1->front_edge) or1->front_edge = &e2; else or1->back_edge = &e2; } if (or2) { if (&e2 == or2->front_edge) or2->front_edge = &e1; else or2->back_edge = &e1; } e1.outrec = or2; e2.outrec = or1; } double Area(OutPt* op) { //https://en.wikipedia.org/wiki/Shoelace_formula double result = 0.0; OutPt* op2 = op; do { result += static_cast(op2->prev->pt.y + op2->pt.y) * static_cast(op2->prev->pt.x - op2->pt.x); op2 = op2->next; } while (op2 != op); return result * 0.5; } inline double AreaTriangle(const Point64& pt1, const Point64& pt2, const Point64& pt3) { return (static_cast(pt3.y + pt1.y) * static_cast(pt3.x - pt1.x) + static_cast(pt1.y + pt2.y) * static_cast(pt1.x - pt2.x) + static_cast(pt2.y + pt3.y) * static_cast(pt2.x - pt3.x)); } void ReverseOutPts(OutPt* op) { if (!op) return; OutPt* op1 = op; OutPt* op2; do { op2 = op1->next; op1->next = op1->prev; op1->prev = op2; op1 = op2; } while (op1 != op); } inline void SwapSides(OutRec& outrec) { Active* e2 = outrec.front_edge; outrec.front_edge = outrec.back_edge; outrec.back_edge = e2; outrec.pts = outrec.pts->next; } inline OutRec* GetRealOutRec(OutRec* outrec) { while (outrec && !outrec->pts) outrec = outrec->owner; return outrec; } inline void UncoupleOutRec(Active ae) { OutRec* outrec = ae.outrec; if (!outrec) return; outrec->front_edge->outrec = nullptr; outrec->back_edge->outrec = nullptr; outrec->front_edge = nullptr; outrec->back_edge = nullptr; } inline bool PtsReallyClose(const Point64& pt1, const Point64& pt2) { return (std::llabs(pt1.x - pt2.x) < 2) && (std::llabs(pt1.y - pt2.y) < 2); } inline bool IsVerySmallTriangle(const OutPt& op) { return op.next->next == op.prev && (PtsReallyClose(op.prev->pt, op.next->pt) || PtsReallyClose(op.pt, op.next->pt) || PtsReallyClose(op.pt, op.prev->pt)); } inline bool IsValidClosedPath(const OutPt* op) { return op && (op->next != op) && (op->next != op->prev) && !IsVerySmallTriangle(*op); } inline bool OutrecIsAscending(const Active* hotEdge) { return (hotEdge == hotEdge->outrec->front_edge); } inline void SwapFrontBackSides(OutRec& outrec) { Active* tmp = outrec.front_edge; outrec.front_edge = outrec.back_edge; outrec.back_edge = tmp; outrec.pts = outrec.pts->next; } inline bool EdgesAdjacentInAEL(const IntersectNode& inode) { return (inode.edge1->next_in_ael == inode.edge2) || (inode.edge1->prev_in_ael == inode.edge2); } inline bool IsJoined(const Active& e) { return e.join_with != JoinWith::None; } inline void SetOwner(OutRec* outrec, OutRec* new_owner) { //precondition1: new_owner is never null while (new_owner->owner && !new_owner->owner->pts) new_owner->owner = new_owner->owner->owner; OutRec* tmp = new_owner; while (tmp && tmp != outrec) tmp = tmp->owner; if (tmp) new_owner->owner = outrec->owner; outrec->owner = new_owner; } //------------------------------------------------------------------------------ // ClipperBase methods ... //------------------------------------------------------------------------------ ClipperBase::~ClipperBase() { Clear(); } void ClipperBase::DeleteEdges(Active*& e) { while (e) { Active* e2 = e; e = e->next_in_ael; delete e2; } } void ClipperBase::CleanUp() { DeleteEdges(actives_); scanline_list_ = std::priority_queue(); intersect_nodes_.clear(); DisposeAllOutRecs(); horz_seg_list_.clear(); horz_join_list_.clear(); } void ClipperBase::Clear() { CleanUp(); DisposeVerticesAndLocalMinima(); current_locmin_iter_ = minima_list_.begin(); minima_list_sorted_ = false; has_open_paths_ = false; } void ClipperBase::Reset() { if (!minima_list_sorted_) { std::sort(minima_list_.begin(), minima_list_.end(), LocMinSorter()); minima_list_sorted_ = true; } LocalMinimaList::const_reverse_iterator i; for (i = minima_list_.rbegin(); i != minima_list_.rend(); ++i) InsertScanline((*i)->vertex->pt.y); current_locmin_iter_ = minima_list_.begin(); actives_ = nullptr; sel_ = nullptr; succeeded_ = true; } #ifdef USINGZ void ClipperBase::SetZ(const Active& e1, const Active& e2, Point64& ip) { if (!zCallback_) return; // prioritize subject over clip vertices by passing // subject vertices before clip vertices in the callback if (GetPolyType(e1) == PathType::Subject) { if (ip == e1.bot) ip.z = e1.bot.z; else if (ip == e1.top) ip.z = e1.top.z; else if (ip == e2.bot) ip.z = e2.bot.z; else if (ip == e2.top) ip.z = e2.top.z; else ip.z = DefaultZ; zCallback_(e1.bot, e1.top, e2.bot, e2.top, ip); } else { if (ip == e2.bot) ip.z = e2.bot.z; else if (ip == e2.top) ip.z = e2.top.z; else if (ip == e1.bot) ip.z = e1.bot.z; else if (ip == e1.top) ip.z = e1.top.z; else ip.z = DefaultZ; zCallback_(e2.bot, e2.top, e1.bot, e1.top, ip); } } #endif void ClipperBase::AddPath(const Path64& path, PathType polytype, bool is_open) { Paths64 tmp; tmp.push_back(path); AddPaths(tmp, polytype, is_open); } void ClipperBase::AddPaths(const Paths64& paths, PathType polytype, bool is_open) { if (is_open) has_open_paths_ = true; minima_list_sorted_ = false; const auto total_vertex_count = std::accumulate(paths.begin(), paths.end(), 0, [](const auto& a, const Path64& path) {return a + static_cast(path.size());}); if (total_vertex_count == 0) return; Vertex* vertices = new Vertex[total_vertex_count], * v = vertices; for (const Path64& path : paths) { //for each path create a circular double linked list of vertices Vertex* v0 = v, * curr_v = v, * prev_v = nullptr; v->prev = nullptr; int cnt = 0; for (const Point64& pt : path) { if (prev_v) { if (prev_v->pt == pt) continue; // ie skips duplicates prev_v->next = curr_v; } curr_v->prev = prev_v; curr_v->pt = pt; curr_v->flags = VertexFlags::None; prev_v = curr_v++; cnt++; } if (!prev_v || !prev_v->prev) continue; if (!is_open && prev_v->pt == v0->pt) prev_v = prev_v->prev; prev_v->next = v0; v0->prev = prev_v; v = curr_v; // ie get ready for next path if (cnt < 2 || (cnt == 2 && !is_open)) continue; //now find and assign local minima bool going_up, going_up0; if (is_open) { curr_v = v0->next; while (curr_v != v0 && curr_v->pt.y == v0->pt.y) curr_v = curr_v->next; going_up = curr_v->pt.y <= v0->pt.y; if (going_up) { v0->flags = VertexFlags::OpenStart; AddLocMin(*v0, polytype, true); } else v0->flags = VertexFlags::OpenStart | VertexFlags::LocalMax; } else // closed path { prev_v = v0->prev; while (prev_v != v0 && prev_v->pt.y == v0->pt.y) prev_v = prev_v->prev; if (prev_v == v0) continue; // only open paths can be completely flat going_up = prev_v->pt.y > v0->pt.y; } going_up0 = going_up; prev_v = v0; curr_v = v0->next; while (curr_v != v0) { if (curr_v->pt.y > prev_v->pt.y && going_up) { prev_v->flags = (prev_v->flags | VertexFlags::LocalMax); going_up = false; } else if (curr_v->pt.y < prev_v->pt.y && !going_up) { going_up = true; AddLocMin(*prev_v, polytype, is_open); } prev_v = curr_v; curr_v = curr_v->next; } if (is_open) { prev_v->flags = prev_v->flags | VertexFlags::OpenEnd; if (going_up) prev_v->flags = prev_v->flags | VertexFlags::LocalMax; else AddLocMin(*prev_v, polytype, is_open); } else if (going_up != going_up0) { if (going_up0) AddLocMin(*prev_v, polytype, false); else prev_v->flags = prev_v->flags | VertexFlags::LocalMax; } } // end processing current path vertex_lists_.emplace_back(vertices); } // end AddPaths inline void ClipperBase::InsertScanline(int64_t y) { scanline_list_.push(y); } bool ClipperBase::PopScanline(int64_t& y) { if (scanline_list_.empty()) return false; y = scanline_list_.top(); scanline_list_.pop(); while (!scanline_list_.empty() && y == scanline_list_.top()) scanline_list_.pop(); // Pop duplicates. return true; } bool ClipperBase::PopLocalMinima(int64_t y, LocalMinima*& local_minima) { if (current_locmin_iter_ == minima_list_.end() || (*current_locmin_iter_)->vertex->pt.y != y) return false; local_minima = (current_locmin_iter_++)->get(); return true; } void ClipperBase::DisposeAllOutRecs() { for (auto outrec : outrec_list_) { if (outrec->pts) DisposeOutPts(outrec); delete outrec; } outrec_list_.resize(0); } void ClipperBase::DisposeVerticesAndLocalMinima() { minima_list_.clear(); for (auto v : vertex_lists_) delete[] v; vertex_lists_.clear(); } void ClipperBase::AddLocMin(Vertex& vert, PathType polytype, bool is_open) { //make sure the vertex is added only once ... if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::None) return; vert.flags = (vert.flags | VertexFlags::LocalMin); minima_list_.push_back(std::make_unique (&vert, polytype, is_open)); } bool ClipperBase::IsContributingClosed(const Active& e) const { switch (fillrule_) { case FillRule::EvenOdd: break; case FillRule::NonZero: if (abs(e.wind_cnt) != 1) return false; break; case FillRule::Positive: if (e.wind_cnt != 1) return false; break; case FillRule::Negative: if (e.wind_cnt != -1) return false; break; } switch (cliptype_) { case ClipType::None: return false; case ClipType::Intersection: switch (fillrule_) { case FillRule::Positive: return (e.wind_cnt2 > 0); case FillRule::Negative: return (e.wind_cnt2 < 0); default: return (e.wind_cnt2 != 0); } break; case ClipType::Union: switch (fillrule_) { case FillRule::Positive: return (e.wind_cnt2 <= 0); case FillRule::Negative: return (e.wind_cnt2 >= 0); default: return (e.wind_cnt2 == 0); } break; case ClipType::Difference: bool result; switch (fillrule_) { case FillRule::Positive: result = (e.wind_cnt2 <= 0); break; case FillRule::Negative: result = (e.wind_cnt2 >= 0); break; default: result = (e.wind_cnt2 == 0); } if (GetPolyType(e) == PathType::Subject) return result; else return !result; break; case ClipType::Xor: return true; break; } return false; // we should never get here } inline bool ClipperBase::IsContributingOpen(const Active& e) const { bool is_in_clip, is_in_subj; switch (fillrule_) { case FillRule::Positive: is_in_clip = e.wind_cnt2 > 0; is_in_subj = e.wind_cnt > 0; break; case FillRule::Negative: is_in_clip = e.wind_cnt2 < 0; is_in_subj = e.wind_cnt < 0; break; default: is_in_clip = e.wind_cnt2 != 0; is_in_subj = e.wind_cnt != 0; } switch (cliptype_) { case ClipType::Intersection: return is_in_clip; case ClipType::Union: return (!is_in_subj && !is_in_clip); default: return !is_in_clip; } } void ClipperBase::SetWindCountForClosedPathEdge(Active& e) { //Wind counts refer to polygon regions not edges, so here an edge's WindCnt //indicates the higher of the wind counts for the two regions touching the //edge. (NB Adjacent regions can only ever have their wind counts differ by //one. Also, open paths have no meaningful wind directions or counts.) Active* e2 = e.prev_in_ael; //find the nearest closed path edge of the same PolyType in AEL (heading left) PathType pt = GetPolyType(e); while (e2 && (GetPolyType(*e2) != pt || IsOpen(*e2))) e2 = e2->prev_in_ael; if (!e2) { e.wind_cnt = e.wind_dx; e2 = actives_; } else if (fillrule_ == FillRule::EvenOdd) { e.wind_cnt = e.wind_dx; e.wind_cnt2 = e2->wind_cnt2; e2 = e2->next_in_ael; } else { //NonZero, positive, or negative filling here ... //if e's WindCnt is in the SAME direction as its WindDx, then polygon //filling will be on the right of 'e'. //NB neither e2.WindCnt nor e2.WindDx should ever be 0. if (e2->wind_cnt * e2->wind_dx < 0) { //opposite directions so 'e' is outside 'e2' ... if (abs(e2->wind_cnt) > 1) { //outside prev poly but still inside another. if (e2->wind_dx * e.wind_dx < 0) //reversing direction so use the same WC e.wind_cnt = e2->wind_cnt; else //otherwise keep 'reducing' the WC by 1 (ie towards 0) ... e.wind_cnt = e2->wind_cnt + e.wind_dx; } else //now outside all polys of same polytype so set own WC ... e.wind_cnt = (IsOpen(e) ? 1 : e.wind_dx); } else { //'e' must be inside 'e2' if (e2->wind_dx * e.wind_dx < 0) //reversing direction so use the same WC e.wind_cnt = e2->wind_cnt; else //otherwise keep 'increasing' the WC by 1 (ie away from 0) ... e.wind_cnt = e2->wind_cnt + e.wind_dx; } e.wind_cnt2 = e2->wind_cnt2; e2 = e2->next_in_ael; // ie get ready to calc WindCnt2 } //update wind_cnt2 ... if (fillrule_ == FillRule::EvenOdd) while (e2 != &e) { if (GetPolyType(*e2) != pt && !IsOpen(*e2)) e.wind_cnt2 = (e.wind_cnt2 == 0 ? 1 : 0); e2 = e2->next_in_ael; } else while (e2 != &e) { if (GetPolyType(*e2) != pt && !IsOpen(*e2)) e.wind_cnt2 += e2->wind_dx; e2 = e2->next_in_ael; } } void ClipperBase::SetWindCountForOpenPathEdge(Active& e) { Active* e2 = actives_; if (fillrule_ == FillRule::EvenOdd) { int cnt1 = 0, cnt2 = 0; while (e2 != &e) { if (GetPolyType(*e2) == PathType::Clip) cnt2++; else if (!IsOpen(*e2)) cnt1++; e2 = e2->next_in_ael; } e.wind_cnt = (IsOdd(cnt1) ? 1 : 0); e.wind_cnt2 = (IsOdd(cnt2) ? 1 : 0); } else { while (e2 != &e) { if (GetPolyType(*e2) == PathType::Clip) e.wind_cnt2 += e2->wind_dx; else if (!IsOpen(*e2)) e.wind_cnt += e2->wind_dx; e2 = e2->next_in_ael; } } } bool IsValidAelOrder(const Active& resident, const Active& newcomer) { if (newcomer.curr_x != resident.curr_x) return newcomer.curr_x > resident.curr_x; //get the turning direction a1.top, a2.bot, a2.top double d = CrossProduct(resident.top, newcomer.bot, newcomer.top); if (d != 0) return d < 0; //edges must be collinear to get here //for starting open paths, place them according to //the direction they're about to turn if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y)) { return CrossProduct(newcomer.bot, resident.top, NextVertex(resident)->pt) <= 0; } else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y)) { return CrossProduct(newcomer.bot, newcomer.top, NextVertex(newcomer)->pt) >= 0; } int64_t y = newcomer.bot.y; bool newcomerIsLeft = newcomer.is_left_bound; if (resident.bot.y != y || resident.local_min->vertex->pt.y != y) return newcomer.is_left_bound; //resident must also have just been inserted else if (resident.is_left_bound != newcomerIsLeft) return newcomerIsLeft; else if (CrossProduct(PrevPrevVertex(resident)->pt, resident.bot, resident.top) == 0) return true; else //compare turning direction of the alternate bound return (CrossProduct(PrevPrevVertex(resident)->pt, newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft; } void ClipperBase::InsertLeftEdge(Active& e) { Active* e2; if (!actives_) { e.prev_in_ael = nullptr; e.next_in_ael = nullptr; actives_ = &e; } else if (!IsValidAelOrder(*actives_, e)) { e.prev_in_ael = nullptr; e.next_in_ael = actives_; actives_->prev_in_ael = &e; actives_ = &e; } else { e2 = actives_; while (e2->next_in_ael && IsValidAelOrder(*e2->next_in_ael, e)) e2 = e2->next_in_ael; if (e2->join_with == JoinWith::Right) e2 = e2->next_in_ael; if (!e2) return; // should never happen and stops compiler warning :) e.next_in_ael = e2->next_in_ael; if (e2->next_in_ael) e2->next_in_ael->prev_in_ael = &e; e.prev_in_ael = e2; e2->next_in_ael = &e; } } void InsertRightEdge(Active& e, Active& e2) { e2.next_in_ael = e.next_in_ael; if (e.next_in_ael) e.next_in_ael->prev_in_ael = &e2; e2.prev_in_ael = &e; e.next_in_ael = &e2; } void ClipperBase::InsertLocalMinimaIntoAEL(int64_t bot_y) { LocalMinima* local_minima; Active* left_bound, * right_bound; //Add any local minima (if any) at BotY ... //nb: horizontal local minima edges should contain locMin.vertex.prev while (PopLocalMinima(bot_y, local_minima)) { if ((local_minima->vertex->flags & VertexFlags::OpenStart) != VertexFlags::None) { left_bound = nullptr; } else { left_bound = new Active(); left_bound->bot = local_minima->vertex->pt; left_bound->curr_x = left_bound->bot.x; left_bound->wind_dx = -1; left_bound->vertex_top = local_minima->vertex->prev; // ie descending left_bound->top = left_bound->vertex_top->pt; left_bound->local_min = local_minima; SetDx(*left_bound); } if ((local_minima->vertex->flags & VertexFlags::OpenEnd) != VertexFlags::None) { right_bound = nullptr; } else { right_bound = new Active(); right_bound->bot = local_minima->vertex->pt; right_bound->curr_x = right_bound->bot.x; right_bound->wind_dx = 1; right_bound->vertex_top = local_minima->vertex->next; // ie ascending right_bound->top = right_bound->vertex_top->pt; right_bound->local_min = local_minima; SetDx(*right_bound); } //Currently LeftB is just the descending bound and RightB is the ascending. //Now if the LeftB isn't on the left of RightB then we need swap them. if (left_bound && right_bound) { if (IsHorizontal(*left_bound)) { if (IsHeadingRightHorz(*left_bound)) SwapActives(left_bound, right_bound); } else if (IsHorizontal(*right_bound)) { if (IsHeadingLeftHorz(*right_bound)) SwapActives(left_bound, right_bound); } else if (left_bound->dx < right_bound->dx) SwapActives(left_bound, right_bound); } else if (!left_bound) { left_bound = right_bound; right_bound = nullptr; } bool contributing; left_bound->is_left_bound = true; InsertLeftEdge(*left_bound); if (IsOpen(*left_bound)) { SetWindCountForOpenPathEdge(*left_bound); contributing = IsContributingOpen(*left_bound); } else { SetWindCountForClosedPathEdge(*left_bound); contributing = IsContributingClosed(*left_bound); } if (right_bound) { right_bound->is_left_bound = false; right_bound->wind_cnt = left_bound->wind_cnt; right_bound->wind_cnt2 = left_bound->wind_cnt2; InsertRightEdge(*left_bound, *right_bound); /////// if (contributing) { AddLocalMinPoly(*left_bound, *right_bound, left_bound->bot, true); if (!IsHorizontal(*left_bound)) CheckJoinLeft(*left_bound, left_bound->bot); } while (right_bound->next_in_ael && IsValidAelOrder(*right_bound->next_in_ael, *right_bound)) { IntersectEdges(*right_bound, *right_bound->next_in_ael, right_bound->bot); SwapPositionsInAEL(*right_bound, *right_bound->next_in_ael); } if (IsHorizontal(*right_bound)) PushHorz(*right_bound); else { CheckJoinRight(*right_bound, right_bound->bot); InsertScanline(right_bound->top.y); } } else if (contributing) { StartOpenPath(*left_bound, left_bound->bot); } if (IsHorizontal(*left_bound)) PushHorz(*left_bound); else InsertScanline(left_bound->top.y); } // while (PopLocalMinima()) } inline void ClipperBase::PushHorz(Active& e) { e.next_in_sel = (sel_ ? sel_ : nullptr); sel_ = &e; } inline bool ClipperBase::PopHorz(Active*& e) { e = sel_; if (!e) return false; sel_ = sel_->next_in_sel; return true; } OutPt* ClipperBase::AddLocalMinPoly(Active& e1, Active& e2, const Point64& pt, bool is_new) { OutRec* outrec = NewOutRec(); e1.outrec = outrec; e2.outrec = outrec; if (IsOpen(e1)) { outrec->owner = nullptr; outrec->is_open = true; if (e1.wind_dx > 0) SetSides(*outrec, e1, e2); else SetSides(*outrec, e2, e1); } else { Active* prevHotEdge = GetPrevHotEdge(e1); //e.windDx is the winding direction of the **input** paths //and unrelated to the winding direction of output polygons. //Output orientation is determined by e.outrec.frontE which is //the ascending edge (see AddLocalMinPoly). if (prevHotEdge) { if (using_polytree_) SetOwner(outrec, prevHotEdge->outrec); if (OutrecIsAscending(prevHotEdge) == is_new) SetSides(*outrec, e2, e1); else SetSides(*outrec, e1, e2); } else { outrec->owner = nullptr; if (is_new) SetSides(*outrec, e1, e2); else SetSides(*outrec, e2, e1); } } OutPt* op = new OutPt(pt, outrec); outrec->pts = op; return op; } OutPt* ClipperBase::AddLocalMaxPoly(Active& e1, Active& e2, const Point64& pt) { if (IsJoined(e1)) Split(e1, pt); if (IsJoined(e2)) Split(e2, pt); if (IsFront(e1) == IsFront(e2)) { if (IsOpenEnd(e1)) SwapFrontBackSides(*e1.outrec); else if (IsOpenEnd(e2)) SwapFrontBackSides(*e2.outrec); else { succeeded_ = false; return nullptr; } } OutPt* result = AddOutPt(e1, pt); if (e1.outrec == e2.outrec) { OutRec& outrec = *e1.outrec; outrec.pts = result; if (using_polytree_) { Active* e = GetPrevHotEdge(e1); if (!e) outrec.owner = nullptr; else SetOwner(&outrec, e->outrec); // nb: outRec.owner here is likely NOT the real // owner but this will be checked in DeepCheckOwner() } UncoupleOutRec(e1); result = outrec.pts; if (outrec.owner && !outrec.owner->front_edge) outrec.owner = GetRealOutRec(outrec.owner); } //and to preserve the winding orientation of outrec ... else if (IsOpen(e1)) { if (e1.wind_dx < 0) JoinOutrecPaths(e1, e2); else JoinOutrecPaths(e2, e1); } else if (e1.outrec->idx < e2.outrec->idx) JoinOutrecPaths(e1, e2); else JoinOutrecPaths(e2, e1); return result; } void ClipperBase::JoinOutrecPaths(Active& e1, Active& e2) { //join e2 outrec path onto e1 outrec path and then delete e2 outrec path //pointers. (NB Only very rarely do the joining ends share the same coords.) OutPt* p1_st = e1.outrec->pts; OutPt* p2_st = e2.outrec->pts; OutPt* p1_end = p1_st->next; OutPt* p2_end = p2_st->next; if (IsFront(e1)) { p2_end->prev = p1_st; p1_st->next = p2_end; p2_st->next = p1_end; p1_end->prev = p2_st; e1.outrec->pts = p2_st; e1.outrec->front_edge = e2.outrec->front_edge; if (e1.outrec->front_edge) e1.outrec->front_edge->outrec = e1.outrec; } else { p1_end->prev = p2_st; p2_st->next = p1_end; p1_st->next = p2_end; p2_end->prev = p1_st; e1.outrec->back_edge = e2.outrec->back_edge; if (e1.outrec->back_edge) e1.outrec->back_edge->outrec = e1.outrec; } //after joining, the e2.OutRec must contains no vertices ... e2.outrec->front_edge = nullptr; e2.outrec->back_edge = nullptr; e2.outrec->pts = nullptr; SetOwner(e2.outrec, e1.outrec); if (IsOpenEnd(e1)) { e2.outrec->pts = e1.outrec->pts; e1.outrec->pts = nullptr; } //and e1 and e2 are maxima and are about to be dropped from the Actives list. e1.outrec = nullptr; e2.outrec = nullptr; } OutRec* ClipperBase::NewOutRec() { OutRec* result = new OutRec(); result->idx = outrec_list_.size(); outrec_list_.push_back(result); result->pts = nullptr; result->owner = nullptr; result->polypath = nullptr; result->is_open = false; return result; } OutPt* ClipperBase::AddOutPt(const Active& e, const Point64& pt) { OutPt* new_op = nullptr; //Outrec.OutPts: a circular doubly-linked-list of POutPt where ... //op_front[.Prev]* ~~~> op_back & op_back == op_front.Next OutRec* outrec = e.outrec; bool to_front = IsFront(e); OutPt* op_front = outrec->pts; OutPt* op_back = op_front->next; if (to_front) { if (pt == op_front->pt) return op_front; } else if (pt == op_back->pt) return op_back; new_op = new OutPt(pt, outrec); op_back->prev = new_op; new_op->prev = op_front; new_op->next = op_back; op_front->next = new_op; if (to_front) outrec->pts = new_op; return new_op; } void ClipperBase::CleanCollinear(OutRec* outrec) { outrec = GetRealOutRec(outrec); if (!outrec || outrec->is_open) return; if (!IsValidClosedPath(outrec->pts)) { DisposeOutPts(outrec); return; } OutPt* startOp = outrec->pts, * op2 = startOp; for (; ; ) { //NB if preserveCollinear == true, then only remove 180 deg. spikes if ((CrossProduct(op2->prev->pt, op2->pt, op2->next->pt) == 0) && (op2->pt == op2->prev->pt || op2->pt == op2->next->pt || !PreserveCollinear || DotProduct(op2->prev->pt, op2->pt, op2->next->pt) < 0)) { if (op2 == outrec->pts) outrec->pts = op2->prev; op2 = DisposeOutPt(op2); if (!IsValidClosedPath(op2)) { DisposeOutPts(outrec); return; } startOp = op2; continue; } op2 = op2->next; if (op2 == startOp) break; } FixSelfIntersects(outrec); } void ClipperBase::DoSplitOp(OutRec* outrec, OutPt* splitOp) { // splitOp.prev -> splitOp && // splitOp.next -> splitOp.next.next are intersecting OutPt* prevOp = splitOp->prev; OutPt* nextNextOp = splitOp->next->next; outrec->pts = prevOp; Point64 ip; GetIntersectPoint(prevOp->pt, splitOp->pt, splitOp->next->pt, nextNextOp->pt, ip); #ifdef USINGZ if (zCallback_) zCallback_(prevOp->pt, splitOp->pt, splitOp->next->pt, nextNextOp->pt, ip); #endif double area1 = Area(outrec->pts); double absArea1 = std::fabs(area1); if (absArea1 < 2) { DisposeOutPts(outrec); return; } // nb: area1 is the path's area *before* splitting, whereas area2 is // the area of the triangle containing splitOp & splitOp.next. // So the only way for these areas to have the same sign is if // the split triangle is larger than the path containing prevOp or // if there's more than one self=intersection. double area2 = AreaTriangle(ip, splitOp->pt, splitOp->next->pt); double absArea2 = std::fabs(area2); // de-link splitOp and splitOp.next from the path // while inserting the intersection point if (ip == prevOp->pt || ip == nextNextOp->pt) { nextNextOp->prev = prevOp; prevOp->next = nextNextOp; } else { OutPt* newOp2 = new OutPt(ip, prevOp->outrec); newOp2->prev = prevOp; newOp2->next = nextNextOp; nextNextOp->prev = newOp2; prevOp->next = newOp2; } if (absArea2 >= 1 && (absArea2 > absArea1 || (area2 > 0) == (area1 > 0))) { OutRec* newOr = NewOutRec(); newOr->owner = outrec->owner; if (using_polytree_) { if (!outrec->splits) outrec->splits = new OutRecList(); outrec->splits->push_back(newOr); } splitOp->outrec = newOr; splitOp->next->outrec = newOr; OutPt* newOp = new OutPt(ip, newOr); newOp->prev = splitOp->next; newOp->next = splitOp; newOr->pts = newOp; splitOp->prev = newOp; splitOp->next->next = newOp; } else { delete splitOp->next; delete splitOp; } } void ClipperBase::FixSelfIntersects(OutRec* outrec) { OutPt* op2 = outrec->pts; for (; ; ) { // triangles can't self-intersect if (op2->prev == op2->next->next) break; if (SegmentsIntersect(op2->prev->pt, op2->pt, op2->next->pt, op2->next->next->pt)) { if (op2 == outrec->pts || op2->next == outrec->pts) outrec->pts = outrec->pts->prev; DoSplitOp(outrec, op2); if (!outrec->pts) break; op2 = outrec->pts; continue; } else op2 = op2->next; if (op2 == outrec->pts) break; } } inline void UpdateOutrecOwner(OutRec* outrec) { OutPt* opCurr = outrec->pts; for (; ; ) { opCurr->outrec = outrec; opCurr = opCurr->next; if (opCurr == outrec->pts) return; } } OutPt* ClipperBase::StartOpenPath(Active& e, const Point64& pt) { OutRec* outrec = NewOutRec(); outrec->is_open = true; if (e.wind_dx > 0) { outrec->front_edge = &e; outrec->back_edge = nullptr; } else { outrec->front_edge = nullptr; outrec->back_edge = &e; } e.outrec = outrec; OutPt* op = new OutPt(pt, outrec); outrec->pts = op; return op; } inline void ClipperBase::UpdateEdgeIntoAEL(Active* e) { e->bot = e->top; e->vertex_top = NextVertex(*e); e->top = e->vertex_top->pt; e->curr_x = e->bot.x; SetDx(*e); if (IsJoined(*e)) Split(*e, e->bot); if (IsHorizontal(*e)) return; InsertScanline(e->top.y); CheckJoinLeft(*e, e->bot); CheckJoinRight(*e, e->bot); } Active* FindEdgeWithMatchingLocMin(Active* e) { Active* result = e->next_in_ael; while (result) { if (result->local_min == e->local_min) return result; else if (!IsHorizontal(*result) && e->bot != result->bot) result = nullptr; else result = result->next_in_ael; } result = e->prev_in_ael; while (result) { if (result->local_min == e->local_min) return result; else if (!IsHorizontal(*result) && e->bot != result->bot) return nullptr; else result = result->prev_in_ael; } return result; } OutPt* ClipperBase::IntersectEdges(Active& e1, Active& e2, const Point64& pt) { //MANAGE OPEN PATH INTERSECTIONS SEPARATELY ... if (has_open_paths_ && (IsOpen(e1) || IsOpen(e2))) { if (IsOpen(e1) && IsOpen(e2)) return nullptr; Active* edge_o, * edge_c; if (IsOpen(e1)) { edge_o = &e1; edge_c = &e2; } else { edge_o = &e2; edge_c = &e1; } if (IsJoined(*edge_c)) Split(*edge_c, pt); // needed for safety if (abs(edge_c->wind_cnt) != 1) return nullptr; switch (cliptype_) { case ClipType::Union: if (!IsHotEdge(*edge_c)) return nullptr; break; default: if (edge_c->local_min->polytype == PathType::Subject) return nullptr; } switch (fillrule_) { case FillRule::Positive: if (edge_c->wind_cnt != 1) return nullptr; break; case FillRule::Negative: if (edge_c->wind_cnt != -1) return nullptr; break; default: if (std::abs(edge_c->wind_cnt) != 1) return nullptr; break; } //toggle contribution ... if (IsHotEdge(*edge_o)) { OutPt* resultOp = AddOutPt(*edge_o, pt); #ifdef USINGZ if (zCallback_) SetZ(e1, e2, resultOp->pt); #endif if (IsFront(*edge_o)) edge_o->outrec->front_edge = nullptr; else edge_o->outrec->back_edge = nullptr; edge_o->outrec = nullptr; return resultOp; } //horizontal edges can pass under open paths at a LocMins else if (pt == edge_o->local_min->vertex->pt && !IsOpenEnd(*edge_o->local_min->vertex)) { //find the other side of the LocMin and //if it's 'hot' join up with it ... Active* e3 = FindEdgeWithMatchingLocMin(edge_o); if (e3 && IsHotEdge(*e3)) { edge_o->outrec = e3->outrec; if (edge_o->wind_dx > 0) SetSides(*e3->outrec, *edge_o, *e3); else SetSides(*e3->outrec, *e3, *edge_o); return e3->outrec->pts; } else return StartOpenPath(*edge_o, pt); } else return StartOpenPath(*edge_o, pt); } //MANAGING CLOSED PATHS FROM HERE ON if (IsJoined(e1)) Split(e1, pt); if (IsJoined(e2)) Split(e2, pt); //UPDATE WINDING COUNTS... int old_e1_windcnt, old_e2_windcnt; if (e1.local_min->polytype == e2.local_min->polytype) { if (fillrule_ == FillRule::EvenOdd) { old_e1_windcnt = e1.wind_cnt; e1.wind_cnt = e2.wind_cnt; e2.wind_cnt = old_e1_windcnt; } else { if (e1.wind_cnt + e2.wind_dx == 0) e1.wind_cnt = -e1.wind_cnt; else e1.wind_cnt += e2.wind_dx; if (e2.wind_cnt - e1.wind_dx == 0) e2.wind_cnt = -e2.wind_cnt; else e2.wind_cnt -= e1.wind_dx; } } else { if (fillrule_ != FillRule::EvenOdd) { e1.wind_cnt2 += e2.wind_dx; e2.wind_cnt2 -= e1.wind_dx; } else { e1.wind_cnt2 = (e1.wind_cnt2 == 0 ? 1 : 0); e2.wind_cnt2 = (e2.wind_cnt2 == 0 ? 1 : 0); } } switch (fillrule_) { case FillRule::EvenOdd: case FillRule::NonZero: old_e1_windcnt = abs(e1.wind_cnt); old_e2_windcnt = abs(e2.wind_cnt); break; default: if (fillrule_ == fillpos) { old_e1_windcnt = e1.wind_cnt; old_e2_windcnt = e2.wind_cnt; } else { old_e1_windcnt = -e1.wind_cnt; old_e2_windcnt = -e2.wind_cnt; } break; } const bool e1_windcnt_in_01 = old_e1_windcnt == 0 || old_e1_windcnt == 1; const bool e2_windcnt_in_01 = old_e2_windcnt == 0 || old_e2_windcnt == 1; if ((!IsHotEdge(e1) && !e1_windcnt_in_01) || (!IsHotEdge(e2) && !e2_windcnt_in_01)) { return nullptr; } //NOW PROCESS THE INTERSECTION ... OutPt* resultOp = nullptr; //if both edges are 'hot' ... if (IsHotEdge(e1) && IsHotEdge(e2)) { if ((old_e1_windcnt != 0 && old_e1_windcnt != 1) || (old_e2_windcnt != 0 && old_e2_windcnt != 1) || (e1.local_min->polytype != e2.local_min->polytype && cliptype_ != ClipType::Xor)) { resultOp = AddLocalMaxPoly(e1, e2, pt); #ifdef USINGZ if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt); #endif } else if (IsFront(e1) || (e1.outrec == e2.outrec)) { //this 'else if' condition isn't strictly needed but //it's sensible to split polygons that ony touch at //a common vertex (not at common edges). resultOp = AddLocalMaxPoly(e1, e2, pt); #ifdef USINGZ OutPt* op2 = AddLocalMinPoly(e1, e2, pt); if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt); if (zCallback_) SetZ(e1, e2, op2->pt); #else AddLocalMinPoly(e1, e2, pt); #endif } else { resultOp = AddOutPt(e1, pt); #ifdef USINGZ OutPt* op2 = AddOutPt(e2, pt); if (zCallback_) { SetZ(e1, e2, resultOp->pt); SetZ(e1, e2, op2->pt); } #else AddOutPt(e2, pt); #endif SwapOutrecs(e1, e2); } } else if (IsHotEdge(e1)) { resultOp = AddOutPt(e1, pt); #ifdef USINGZ if (zCallback_) SetZ(e1, e2, resultOp->pt); #endif SwapOutrecs(e1, e2); } else if (IsHotEdge(e2)) { resultOp = AddOutPt(e2, pt); #ifdef USINGZ if (zCallback_) SetZ(e1, e2, resultOp->pt); #endif SwapOutrecs(e1, e2); } else { int64_t e1Wc2, e2Wc2; switch (fillrule_) { case FillRule::EvenOdd: case FillRule::NonZero: e1Wc2 = abs(e1.wind_cnt2); e2Wc2 = abs(e2.wind_cnt2); break; default: if (fillrule_ == fillpos) { e1Wc2 = e1.wind_cnt2; e2Wc2 = e2.wind_cnt2; } else { e1Wc2 = -e1.wind_cnt2; e2Wc2 = -e2.wind_cnt2; } break; } if (!IsSamePolyType(e1, e2)) { resultOp = AddLocalMinPoly(e1, e2, pt, false); #ifdef USINGZ if (zCallback_) SetZ(e1, e2, resultOp->pt); #endif } else if (old_e1_windcnt == 1 && old_e2_windcnt == 1) { resultOp = nullptr; switch (cliptype_) { case ClipType::Union: if (e1Wc2 <= 0 && e2Wc2 <= 0) resultOp = AddLocalMinPoly(e1, e2, pt, false); break; case ClipType::Difference: if (((GetPolyType(e1) == PathType::Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || ((GetPolyType(e1) == PathType::Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) { resultOp = AddLocalMinPoly(e1, e2, pt, false); } break; case ClipType::Xor: resultOp = AddLocalMinPoly(e1, e2, pt, false); break; default: if (e1Wc2 > 0 && e2Wc2 > 0) resultOp = AddLocalMinPoly(e1, e2, pt, false); break; } #ifdef USINGZ if (resultOp && zCallback_) SetZ(e1, e2, resultOp->pt); #endif } } return resultOp; } inline void ClipperBase::DeleteFromAEL(Active& e) { Active* prev = e.prev_in_ael; Active* next = e.next_in_ael; if (!prev && !next && (&e != actives_)) return; // already deleted if (prev) prev->next_in_ael = next; else actives_ = next; if (next) next->prev_in_ael = prev; delete& e; } inline void ClipperBase::AdjustCurrXAndCopyToSEL(const int64_t top_y) { Active* e = actives_; sel_ = e; while (e) { e->prev_in_sel = e->prev_in_ael; e->next_in_sel = e->next_in_ael; e->jump = e->next_in_sel; if (e->join_with == JoinWith::Left) e->curr_x = e->prev_in_ael->curr_x; // also avoids complications else e->curr_x = TopX(*e, top_y); e = e->next_in_ael; } } bool ClipperBase::ExecuteInternal(ClipType ct, FillRule fillrule, bool use_polytrees) { cliptype_ = ct; fillrule_ = fillrule; using_polytree_ = use_polytrees; Reset(); int64_t y; if (ct == ClipType::None || !PopScanline(y)) return true; while (succeeded_) { InsertLocalMinimaIntoAEL(y); Active* e; while (PopHorz(e)) DoHorizontal(*e); if (horz_seg_list_.size() > 0) { ConvertHorzSegsToJoins(); horz_seg_list_.clear(); } bot_y_ = y; // bot_y_ == bottom of scanbeam if (!PopScanline(y)) break; // y new top of scanbeam DoIntersections(y); DoTopOfScanbeam(y); while (PopHorz(e)) DoHorizontal(*e); } if (succeeded_) ProcessHorzJoins(); return succeeded_; } inline void FixOutRecPts(OutRec* outrec) { OutPt* op = outrec->pts; do { op->outrec = outrec; op = op->next; } while (op != outrec->pts); } inline Rect64 GetBounds(OutPt* op) { Rect64 result(op->pt.x, op->pt.y, op->pt.x, op->pt.y); OutPt* op2 = op->next; while (op2 != op) { if (op2->pt.x < result.left) result.left = op2->pt.x; else if (op2->pt.x > result.right) result.right = op2->pt.x; if (op2->pt.y < result.top) result.top = op2->pt.y; else if (op2->pt.y > result.bottom) result.bottom = op2->pt.y; op2 = op2->next; } return result; } static PointInPolygonResult PointInOpPolygon(const Point64& pt, OutPt* op) { if (op == op->next || op->prev == op->next) return PointInPolygonResult::IsOutside; OutPt* op2 = op; do { if (op->pt.y != pt.y) break; op = op->next; } while (op != op2); if (op == op2) return PointInPolygonResult::IsOutside; // must be above or below to get here bool isAbove = op->pt.y < pt.y; int val = 0; op2 = op->next; while (op2 != op) { if (isAbove) while (op2 != op && op2->pt.y < pt.y) op2 = op2->next; else while (op2 != op && op2->pt.y > pt.y) op2 = op2->next; if (op2 == op) break; // must have touched or crossed the pt.Y horizonal // and this must happen an even number of times if (op2->pt.y == pt.y) // touching the horizontal { if (op2->pt.x == pt.x || (op2->pt.y == op2->prev->pt.y && (pt.x < op2->prev->pt.x) != (pt.x < op2->pt.x))) return PointInPolygonResult::IsOn; op2 = op2->next; continue; } if (pt.x < op2->pt.x && pt.x < op2->prev->pt.x); // do nothing because // we're only interested in edges crossing on the left else if ((pt.x > op2->prev->pt.x && pt.x > op2->pt.x)) val = 1 - val; // toggle val else { double d = CrossProduct(op2->prev->pt, op2->pt, pt); if (d == 0) return PointInPolygonResult::IsOn; if ((d < 0) == isAbove) val = 1 - val; } isAbove = !isAbove; op2 = op2->next; } if (val == 0) return PointInPolygonResult::IsOutside; else return PointInPolygonResult::IsInside; } inline bool Path1InsidePath2(OutPt* op1, OutPt* op2) { // we need to make some accommodation for rounding errors // so we won't jump if the first vertex is found outside int outside_cnt = 0; OutPt* op = op1; do { PointInPolygonResult result = PointInOpPolygon(op->pt, op2); if (result == PointInPolygonResult::IsOutside) ++outside_cnt; else if (result == PointInPolygonResult::IsInside) --outside_cnt; op = op->next; } while (op != op1 && std::abs(outside_cnt) < 2); if (std::abs(outside_cnt) > 1) return (outside_cnt < 0); // since path1's location is still equivocal, check its midpoint Point64 mp = GetBounds(op).MidPoint(); return PointInOpPolygon(mp, op2) == PointInPolygonResult::IsInside; } inline bool Path1InsidePath2(const OutRec* or1, const OutRec* or2) { // we need to make some accommodation for rounding errors // so we won't jump if the first vertex is found outside int outside_cnt = 0; OutPt* op = or1->pts; do { PointInPolygonResult result = PointInPolygon(op->pt, or2->path); if (result == PointInPolygonResult::IsOutside) ++outside_cnt; else if (result == PointInPolygonResult::IsInside) --outside_cnt; op = op->next; } while (op != or1->pts && std::abs(outside_cnt) < 2); if (std::abs(outside_cnt) > 1) return (outside_cnt < 0); // since path1's location is still equivocal, check its midpoint Point64 mp = GetBounds(op).MidPoint(); return PointInPolygon(mp, or2->path) == PointInPolygonResult::IsInside; } inline bool SetHorzSegHeadingForward(HorzSegment& hs, OutPt* opP, OutPt* opN) { if (opP->pt.x == opN->pt.x) return false; if (opP->pt.x < opN->pt.x) { hs.left_op = opP; hs.right_op = opN; hs.left_to_right = true; } else { hs.left_op = opN; hs.right_op = opP; hs.left_to_right = false; } return true; } inline bool UpdateHorzSegment(HorzSegment& hs) { OutPt* op = hs.left_op; OutRec* outrec = GetRealOutRec(op->outrec); bool outrecHasEdges = outrec->front_edge; int64_t curr_y = op->pt.y; OutPt* opP = op, * opN = op; if (outrecHasEdges) { OutPt* opA = outrec->pts, * opZ = opA->next; while (opP != opZ && opP->prev->pt.y == curr_y) opP = opP->prev; while (opN != opA && opN->next->pt.y == curr_y) opN = opN->next; } else { while (opP->prev != opN && opP->prev->pt.y == curr_y) opP = opP->prev; while (opN->next != opP && opN->next->pt.y == curr_y) opN = opN->next; } bool result = SetHorzSegHeadingForward(hs, opP, opN) && !hs.left_op->horz; if (result) hs.left_op->horz = &hs; else hs.right_op = nullptr; // (for sorting) return result; } void ClipperBase::ConvertHorzSegsToJoins() { auto j = std::count_if(horz_seg_list_.begin(), horz_seg_list_.end(), [](HorzSegment& hs) { return UpdateHorzSegment(hs); }); if (j < 2) return; std::sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter()); HorzSegmentList::iterator hs1 = horz_seg_list_.begin(), hs2; HorzSegmentList::iterator hs_end = hs1 +j; HorzSegmentList::iterator hs_end1 = hs_end - 1; for (; hs1 != hs_end1; ++hs1) { for (hs2 = hs1 + 1; hs2 != hs_end; ++hs2) { if (hs2->left_op->pt.x >= hs1->right_op->pt.x) break; if (hs2->left_to_right == hs1->left_to_right || (hs2->right_op->pt.x <= hs1->left_op->pt.x)) continue; int64_t curr_y = hs1->left_op->pt.y; if (hs1->left_to_right) { while (hs1->left_op->next->pt.y == curr_y && hs1->left_op->next->pt.x <= hs2->left_op->pt.x) hs1->left_op = hs1->left_op->next; while (hs2->left_op->prev->pt.y == curr_y && hs2->left_op->prev->pt.x <= hs1->left_op->pt.x) hs2->left_op = hs2->left_op->prev; HorzJoin join = HorzJoin( DuplicateOp(hs1->left_op, true), DuplicateOp(hs2->left_op, false)); horz_join_list_.push_back(join); } else { while (hs1->left_op->prev->pt.y == curr_y && hs1->left_op->prev->pt.x <= hs2->left_op->pt.x) hs1->left_op = hs1->left_op->prev; while (hs2->left_op->next->pt.y == curr_y && hs2->left_op->next->pt.x <= hs1->left_op->pt.x) hs2->left_op = hs2->left_op->next; HorzJoin join = HorzJoin( DuplicateOp(hs2->left_op, true), DuplicateOp(hs1->left_op, false)); horz_join_list_.push_back(join); } } } } void ClipperBase::ProcessHorzJoins() { for (const HorzJoin& j : horz_join_list_) { OutRec* or1 = GetRealOutRec(j.op1->outrec); OutRec* or2 = GetRealOutRec(j.op2->outrec); OutPt* op1b = j.op1->next; OutPt* op2b = j.op2->prev; j.op1->next = j.op2; j.op2->prev = j.op1; op1b->prev = op2b; op2b->next = op1b; if (or1 == or2) { or2 = new OutRec(); or2->pts = op1b; FixOutRecPts(or2); if (or1->pts->outrec == or2) { or1->pts = j.op1; or1->pts->outrec = or1; } if (using_polytree_) { if (Path1InsidePath2(or2->pts, or1->pts)) SetOwner(or2, or1); else if (Path1InsidePath2(or1->pts, or2->pts)) SetOwner(or1, or2); else or2->owner = or1; } else or2->owner = or1; outrec_list_.push_back(or2); } else { or2->pts = nullptr; if (using_polytree_) SetOwner(or2, or1); else or2->owner = or1; } } } void ClipperBase::DoIntersections(const int64_t top_y) { if (BuildIntersectList(top_y)) { ProcessIntersectList(); intersect_nodes_.clear(); } } void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y) { Point64 ip; if (!GetIntersectPoint(e1.bot, e1.top, e2.bot, e2.top, ip)) ip = Point64(e1.curr_x, top_y); //parallel edges //rounding errors can occasionally place the calculated intersection //point either below or above the scanbeam, so check and correct ... if (ip.y > bot_y_ || ip.y < top_y) { double abs_dx1 = std::fabs(e1.dx); double abs_dx2 = std::fabs(e2.dx); if (abs_dx1 > 100 && abs_dx2 > 100) { if (abs_dx1 > abs_dx2) ip = GetClosestPointOnSegment(ip, e1.bot, e1.top); else ip = GetClosestPointOnSegment(ip, e2.bot, e2.top); } else if (abs_dx1 > 100) ip = GetClosestPointOnSegment(ip, e1.bot, e1.top); else if (abs_dx2 > 100) ip = GetClosestPointOnSegment(ip, e2.bot, e2.top); else { if (ip.y < top_y) ip.y = top_y; else ip.y = bot_y_; if (abs_dx1 < abs_dx2) ip.x = TopX(e1, ip.y); else ip.x = TopX(e2, ip.y); } } intersect_nodes_.push_back(IntersectNode(&e1, &e2, ip)); } bool ClipperBase::BuildIntersectList(const int64_t top_y) { if (!actives_ || !actives_->next_in_ael) return false; //Calculate edge positions at the top of the current scanbeam, and from this //we will determine the intersections required to reach these new positions. AdjustCurrXAndCopyToSEL(top_y); //Find all edge intersections in the current scanbeam using a stable merge //sort that ensures only adjacent edges are intersecting. Intersect info is //stored in FIntersectList ready to be processed in ProcessIntersectList. //Re merge sorts see https://stackoverflow.com/a/46319131/359538 Active* left = sel_, * right, * l_end, * r_end, * curr_base, * tmp; while (left && left->jump) { Active* prev_base = nullptr; while (left && left->jump) { curr_base = left; right = left->jump; l_end = right; r_end = right->jump; left->jump = r_end; while (left != l_end && right != r_end) { if (right->curr_x < left->curr_x) { tmp = right->prev_in_sel; for (; ; ) { AddNewIntersectNode(*tmp, *right, top_y); if (tmp == left) break; tmp = tmp->prev_in_sel; } tmp = right; right = ExtractFromSEL(tmp); l_end = right; Insert1Before2InSEL(tmp, left); if (left == curr_base) { curr_base = tmp; curr_base->jump = r_end; if (!prev_base) sel_ = curr_base; else prev_base->jump = curr_base; } } else left = left->next_in_sel; } prev_base = curr_base; left = r_end; } left = sel_; } return intersect_nodes_.size() > 0; } void ClipperBase::ProcessIntersectList() { //We now have a list of intersections required so that edges will be //correctly positioned at the top of the scanbeam. However, it's important //that edge intersections are processed from the bottom up, but it's also //crucial that intersections only occur between adjacent edges. //First we do a quicksort so intersections proceed in a bottom up order ... std::sort(intersect_nodes_.begin(), intersect_nodes_.end(), IntersectListSort); //Now as we process these intersections, we must sometimes adjust the order //to ensure that intersecting edges are always adjacent ... IntersectNodeList::iterator node_iter, node_iter2; for (node_iter = intersect_nodes_.begin(); node_iter != intersect_nodes_.end(); ++node_iter) { if (!EdgesAdjacentInAEL(*node_iter)) { node_iter2 = node_iter + 1; while (!EdgesAdjacentInAEL(*node_iter2)) ++node_iter2; std::swap(*node_iter, *node_iter2); } IntersectNode& node = *node_iter; IntersectEdges(*node.edge1, *node.edge2, node.pt); SwapPositionsInAEL(*node.edge1, *node.edge2); node.edge1->curr_x = node.pt.x; node.edge2->curr_x = node.pt.x; CheckJoinLeft(*node.edge2, node.pt); CheckJoinRight(*node.edge1, node.pt, true); } } void ClipperBase::SwapPositionsInAEL(Active& e1, Active& e2) { //preconditon: e1 must be immediately to the left of e2 Active* next = e2.next_in_ael; if (next) next->prev_in_ael = &e1; Active* prev = e1.prev_in_ael; if (prev) prev->next_in_ael = &e2; e2.prev_in_ael = prev; e2.next_in_ael = &e1; e1.prev_in_ael = &e2; e1.next_in_ael = next; if (!e2.prev_in_ael) actives_ = &e2; } inline OutPt* GetLastOp(const Active& hot_edge) { OutRec* outrec = hot_edge.outrec; OutPt* result = outrec->pts; if (&hot_edge != outrec->front_edge) result = result->next; return result; } void ClipperBase::AddTrialHorzJoin(OutPt* op) { if (op->outrec->is_open) return; horz_seg_list_.push_back(HorzSegment(op)); } bool ClipperBase::ResetHorzDirection(const Active& horz, const Vertex* max_vertex, int64_t& horz_left, int64_t& horz_right) { if (horz.bot.x == horz.top.x) { //the horizontal edge is going nowhere ... horz_left = horz.curr_x; horz_right = horz.curr_x; Active* e = horz.next_in_ael; while (e && e->vertex_top != max_vertex) e = e->next_in_ael; return e != nullptr; } else if (horz.curr_x < horz.top.x) { horz_left = horz.curr_x; horz_right = horz.top.x; return true; } else { horz_left = horz.top.x; horz_right = horz.curr_x; return false; // right to left } } inline bool HorzIsSpike(const Active& horzEdge) { Point64 nextPt = NextVertex(horzEdge)->pt; return (nextPt.y == horzEdge.bot.y) && (horzEdge.bot.x < horzEdge.top.x) != (horzEdge.top.x < nextPt.x); } inline void TrimHorz(Active& horzEdge, bool preserveCollinear) { bool wasTrimmed = false; Point64 pt = NextVertex(horzEdge)->pt; while (pt.y == horzEdge.top.y) { //always trim 180 deg. spikes (in closed paths) //but otherwise break if preserveCollinear = true if (preserveCollinear && ((pt.x < horzEdge.top.x) != (horzEdge.bot.x < horzEdge.top.x))) break; horzEdge.vertex_top = NextVertex(horzEdge); horzEdge.top = pt; wasTrimmed = true; if (IsMaxima(horzEdge)) break; pt = NextVertex(horzEdge)->pt; } if (wasTrimmed) SetDx(horzEdge); // +/-infinity } void ClipperBase::DoHorizontal(Active& horz) /******************************************************************************* * Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or * * bottom of a scanbeam) are processed as if layered.The order in which HEs * * are processed doesn't matter. HEs intersect with the bottom vertices of * * other HEs[#] and with non-horizontal edges [*]. Once these intersections * * are completed, intermediate HEs are 'promoted' to the next edge in their * * bounds, and they in turn may be intersected[%] by other HEs. * * * * eg: 3 horizontals at a scanline: / | / / * * | / | (HE3)o ========%========== o * * o ======= o(HE2) / | / / * * o ============#=========*======*========#=========o (HE1) * * / | / | / * *******************************************************************************/ { Point64 pt; bool horzIsOpen = IsOpen(horz); int64_t y = horz.bot.y; Vertex* vertex_max; if (horzIsOpen) vertex_max = GetCurrYMaximaVertex_Open(horz); else vertex_max = GetCurrYMaximaVertex(horz); // remove 180 deg.spikes and also simplify // consecutive horizontals when PreserveCollinear = true if (vertex_max && !horzIsOpen && vertex_max != horz.vertex_top) TrimHorz(horz, PreserveCollinear); int64_t horz_left, horz_right; bool is_left_to_right = ResetHorzDirection(horz, vertex_max, horz_left, horz_right); if (IsHotEdge(horz)) { #ifdef USINGZ OutPt* op = AddOutPt(horz, Point64(horz.curr_x, y, horz.bot.z)); #else OutPt* op = AddOutPt(horz, Point64(horz.curr_x, y)); #endif AddTrialHorzJoin(op); } OutRec* currHorzOutrec = horz.outrec; while (true) // loop through consec. horizontal edges { Active* e; if (is_left_to_right) e = horz.next_in_ael; else e = horz.prev_in_ael; while (e) { if (e->vertex_top == vertex_max) { if (IsHotEdge(horz) && IsJoined(*e)) Split(*e, e->top); if (IsHotEdge(horz)) { while (horz.vertex_top != vertex_max) { AddOutPt(horz, horz.top); UpdateEdgeIntoAEL(&horz); } if (is_left_to_right) AddLocalMaxPoly(horz, *e, horz.top); else AddLocalMaxPoly(*e, horz, horz.top); } DeleteFromAEL(*e); DeleteFromAEL(horz); return; } //if horzEdge is a maxima, keep going until we reach //its maxima pair, otherwise check for break conditions if (vertex_max != horz.vertex_top || IsOpenEnd(horz)) { //otherwise stop when 'ae' is beyond the end of the horizontal line if ((is_left_to_right && e->curr_x > horz_right) || (!is_left_to_right && e->curr_x < horz_left)) break; if (e->curr_x == horz.top.x && !IsHorizontal(*e)) { pt = NextVertex(horz)->pt; if (is_left_to_right) { //with open paths we'll only break once past horz's end if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e)) { if (TopX(*e, pt.y) > pt.x) break; } //otherwise we'll only break when horz's outslope is greater than e's else if (TopX(*e, pt.y) >= pt.x) break; } else { if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e)) { if (TopX(*e, pt.y) < pt.x) break; } else if (TopX(*e, pt.y) <= pt.x) break; } } } pt = Point64(e->curr_x, horz.bot.y); if (is_left_to_right) { IntersectEdges(horz, *e, pt); SwapPositionsInAEL(horz, *e); horz.curr_x = e->curr_x; e = horz.next_in_ael; } else { IntersectEdges(*e, horz, pt); SwapPositionsInAEL(*e, horz); horz.curr_x = e->curr_x; e = horz.prev_in_ael; } if (horz.outrec && horz.outrec != currHorzOutrec) { currHorzOutrec = horz.outrec; //nb: The outrec containining the op returned by IntersectEdges //above may no longer be associated with horzEdge. AddTrialHorzJoin(GetLastOp(horz)); } } //check if we've finished with (consecutive) horizontals ... if (horzIsOpen && IsOpenEnd(horz)) // ie open at top { if (IsHotEdge(horz)) { AddOutPt(horz, horz.top); if (IsFront(horz)) horz.outrec->front_edge = nullptr; else horz.outrec->back_edge = nullptr; horz.outrec = nullptr; } DeleteFromAEL(horz); return; } else if (NextVertex(horz)->pt.y != horz.top.y) break; //still more horizontals in bound to process ... if (IsHotEdge(horz)) AddOutPt(horz, horz.top); UpdateEdgeIntoAEL(&horz); if (PreserveCollinear && !horzIsOpen && HorzIsSpike(horz)) TrimHorz(horz, true); is_left_to_right = ResetHorzDirection(horz, vertex_max, horz_left, horz_right); } if (IsHotEdge(horz)) AddOutPt(horz, horz.top); UpdateEdgeIntoAEL(&horz); // end of an intermediate horiz. } void ClipperBase::DoTopOfScanbeam(const int64_t y) { sel_ = nullptr; // sel_ is reused to flag horizontals (see PushHorz below) Active* e = actives_; while (e) { //nb: 'e' will never be horizontal here if (e->top.y == y) { e->curr_x = e->top.x; if (IsMaxima(*e)) { e = DoMaxima(*e); // TOP OF BOUND (MAXIMA) continue; } else { //INTERMEDIATE VERTEX ... if (IsHotEdge(*e)) AddOutPt(*e, e->top); UpdateEdgeIntoAEL(e); if (IsHorizontal(*e)) PushHorz(*e); // horizontals are processed later } } else // i.e. not the top of the edge e->curr_x = TopX(*e, y); e = e->next_in_ael; } } Active* ClipperBase::DoMaxima(Active& e) { Active* next_e, * prev_e, * max_pair; prev_e = e.prev_in_ael; next_e = e.next_in_ael; if (IsOpenEnd(e)) { if (IsHotEdge(e)) AddOutPt(e, e.top); if (!IsHorizontal(e)) { if (IsHotEdge(e)) { if (IsFront(e)) e.outrec->front_edge = nullptr; else e.outrec->back_edge = nullptr; e.outrec = nullptr; } DeleteFromAEL(e); } return next_e; } max_pair = GetMaximaPair(e); if (!max_pair) return next_e; // eMaxPair is horizontal if (IsJoined(e)) Split(e, e.top); if (IsJoined(*max_pair)) Split(*max_pair, max_pair->top); //only non-horizontal maxima here. //process any edges between maxima pair ... while (next_e != max_pair) { IntersectEdges(e, *next_e, e.top); SwapPositionsInAEL(e, *next_e); next_e = e.next_in_ael; } if (IsOpen(e)) { if (IsHotEdge(e)) AddLocalMaxPoly(e, *max_pair, e.top); DeleteFromAEL(*max_pair); DeleteFromAEL(e); return (prev_e ? prev_e->next_in_ael : actives_); } // e.next_in_ael== max_pair ... if (IsHotEdge(e)) AddLocalMaxPoly(e, *max_pair, e.top); DeleteFromAEL(e); DeleteFromAEL(*max_pair); return (prev_e ? prev_e->next_in_ael : actives_); } void ClipperBase::Split(Active& e, const Point64& pt) { if (e.join_with == JoinWith::Right) { e.join_with = JoinWith::None; e.next_in_ael->join_with = JoinWith::None; AddLocalMinPoly(e, *e.next_in_ael, pt, true); } else { e.join_with = JoinWith::None; e.prev_in_ael->join_with = JoinWith::None; AddLocalMinPoly(*e.prev_in_ael, e, pt, true); } } void ClipperBase::CheckJoinLeft(Active& e, const Point64& pt) { Active* prev = e.prev_in_ael; if (IsOpen(e) || !IsHotEdge(e) || !prev || IsOpen(*prev) || !IsHotEdge(*prev) || e.curr_x != prev->curr_x || pt.y <= e.top.y || pt.y <= prev->top.y || IsJoined(e) || IsOpen(e) || CrossProduct(e.top, pt, prev->top)) return; if (e.outrec->idx == prev->outrec->idx) AddLocalMaxPoly(*prev, e, pt); else if (e.outrec->idx < prev->outrec->idx) JoinOutrecPaths(e, *prev); else JoinOutrecPaths(*prev, e); prev->join_with = JoinWith::Right; e.join_with = JoinWith::Left; } void ClipperBase::CheckJoinRight(Active& e, const Point64& pt, bool check_curr_x) { Active* next = e.next_in_ael; if (IsOpen(e) || !IsHotEdge(e) || IsJoined(e) || !next || IsOpen(*next) || !IsHotEdge(*next) || pt.y < e.top.y +2 || pt.y < next->top.y +2) // avoids trivial joins return; if (check_curr_x) next->curr_x = TopX(*next, pt.y); if (e.curr_x != next->curr_x || CrossProduct(e.top, pt, next->top)) return; if (e.outrec->idx == next->outrec->idx) AddLocalMaxPoly(e, *next, pt); else if (e.outrec->idx < next->outrec->idx) JoinOutrecPaths(e, *next); else JoinOutrecPaths(*next, e); e.join_with = JoinWith::Right; next->join_with = JoinWith::Left; } inline bool GetHorzExtendedHorzSeg(OutPt*& op, OutPt*& op2) { OutRec* outrec = GetRealOutRec(op->outrec); op2 = op; if (outrec->front_edge) { while (op->prev != outrec->pts && op->prev->pt.y == op->pt.y) op = op->prev; while (op2 != outrec->pts && op2->next->pt.y == op2->pt.y) op2 = op2->next; return op2 != op; } else { while (op->prev != op2 && op->prev->pt.y == op->pt.y) op = op->prev; while (op2->next != op && op2->next->pt.y == op2->pt.y) op2 = op2->next; return op2 != op && op2->next != op; } } inline Rect64 GetBounds(const Path64& path) { if (path.empty()) return Rect64(); Rect64 result = invalid_rect; for (const Point64& pt : path) { if (pt.x < result.left) result.left = pt.x; if (pt.x > result.right) result.right = pt.x; if (pt.y < result.top) result.top = pt.y; if (pt.y > result.bottom) result.bottom = pt.y; } return result; } bool BuildPath64(OutPt* op, bool reverse, bool isOpen, Path64& path) { if (!op || 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; } if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false; else return true; } bool ClipperBase::CheckBounds(OutRec* outrec) { if (!outrec->pts) return false; if (!outrec->bounds.IsEmpty()) return true; CleanCollinear(outrec); if (!outrec->pts || !BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)) return false; outrec->bounds = GetBounds(outrec->path); return true; } void ClipperBase::RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath) { // pre-condition: outrec will have valid bounds // post-condition: if a valid path, outrec will have a polypath if (outrec->polypath || outrec->bounds.IsEmpty()) return; while (outrec->owner && (!outrec->owner->pts || !CheckBounds(outrec->owner))) outrec->owner = outrec->owner->owner; if (outrec->owner && !outrec->owner->polypath) RecursiveCheckOwners(outrec->owner, polypath); while (outrec->owner) if (outrec->owner->bounds.Contains(outrec->bounds) && Path1InsidePath2(outrec, outrec->owner)) break; // found - owner contain outrec! else outrec->owner = outrec->owner->owner; if (outrec->owner) outrec->polypath = outrec->owner->polypath->AddChild(outrec->path); else outrec->polypath = polypath->AddChild(outrec->path); } void ClipperBase::DeepCheckOwners(OutRec* outrec, PolyPath* polypath) { RecursiveCheckOwners(outrec, polypath); while (outrec->owner && outrec->owner->splits) { OutRec* split = nullptr; for (auto s : *outrec->owner->splits) { split = GetRealOutRec(s); if (split && split != outrec && split != outrec->owner && CheckBounds(split) && split->bounds.Contains(outrec->bounds) && Path1InsidePath2(outrec, split)) { RecursiveCheckOwners(split, polypath); outrec->owner = split; //found in split break; // inner 'for' loop } else split = nullptr; } if (!split) break; } } 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()); } // nb: outrec_list_.size() may change in the following // while loop because polygons may be split during // calls to CleanCollinear which calls FixSelfIntersects size_t i = 0; while (i < outrec_list_.size()) { OutRec* outrec = outrec_list_[i++]; 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 { // nb: CleanCollinear can add to outrec_list_ CleanCollinear(outrec); //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); if (has_open_paths_) open_paths.reserve(outrec_list_.size()); size_t i = 0; // outrec_list_.size() is not static here because // CheckBounds below can indirectly add additional // OutRec (via FixOutRecPts & CleanCollinear) while (i < outrec_list_.size()) { OutRec* outrec = outrec_list_[i++]; if (!outrec || !outrec->pts) continue; if (outrec->is_open) { Path64 path; if (BuildPath64(outrec->pts, ReverseSolution, true, path)) open_paths.push_back(path); continue; } if (CheckBounds(outrec)) DeepCheckOwners(outrec, &polytree); } } bool BuildPathD(OutPt* op, bool reverse, bool isOpen, PathD& path, double inv_scale) { if (!op || 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; } #ifdef USINGZ path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z)); #else path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale)); #endif while (op2 != op) { if (op2->pt != lastPt) { lastPt = op2->pt; #ifdef USINGZ path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z)); #else path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale)); #endif } if (reverse) op2 = op2->prev; else op2 = op2->next; } if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false; 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 { CleanCollinear(outrec); //closed paths should always return a Positive orientation if (BuildPathD(outrec->pts, ReverseSolution, false, path, invScale_)) solutionClosed.emplace_back(std::move(path)); } } } void ClipperD::BuildTreeD(PolyPathD& polytree, PathsD& open_paths) { 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 (CheckBounds(outrec)) DeepCheckOwners(outrec, &polytree); } } } // namespace clipper2lib