Path: blob/master/thirdparty/clipper2/src/clipper.engine.cpp
9903 views
/*******************************************************************************1* Author : Angus Johnson *2* Date : 30 May 2025 *3* Website : https://www.angusj.com *4* Copyright : Angus Johnson 2010-2025 *5* Purpose : This is the main polygon clipping module *6* License : https://www.boost.org/LICENSE_1_0.txt *7*******************************************************************************/89#include "clipper2/clipper.engine.h"10#include "clipper2/clipper.h"11#include <stdexcept>1213// https://github.com/AngusJohnson/Clipper2/discussions/33414// #discussioncomment-424860215#if defined(_MSC_VER) && ( defined(_M_AMD64) || defined(_M_X64) )16#include <xmmintrin.h>17#include <emmintrin.h>18#define fmin(a,b) _mm_cvtsd_f64(_mm_min_sd(_mm_set_sd(a),_mm_set_sd(b)))19#define fmax(a,b) _mm_cvtsd_f64(_mm_max_sd(_mm_set_sd(a),_mm_set_sd(b)))20#define nearbyint(a) _mm_cvtsd_si64(_mm_set_sd(a)) /* Note: expression type is (int64_t) */21#endif2223namespace Clipper2Lib {2425static const Rect64 invalid_rect = Rect64(false);2627// Every closed path (ie polygon) is made up of a series of vertices forming edge28// 'bounds' that alternate between ascending bounds (containing edges going up29// relative to the Y-axis) and descending bounds. 'Local Minima' refers to30// vertices where ascending and descending bounds join at the bottom, and31// 'Local Maxima' are where ascending and descending bounds join at the top.3233struct Scanline {34int64_t y = 0;35Scanline* next = nullptr;3637explicit Scanline(int64_t y_) : y(y_) {}38};3940struct HorzSegSorter {41inline bool operator()(const HorzSegment& hs1, const HorzSegment& hs2)42{43if (!hs1.right_op || !hs2.right_op) return (hs1.right_op);44return hs2.left_op->pt.x > hs1.left_op->pt.x;45}46};4748struct LocMinSorter {49inline bool operator()(const LocalMinima_ptr& locMin1,50const LocalMinima_ptr& locMin2)51{52if (locMin2->vertex->pt.y != locMin1->vertex->pt.y)53return locMin2->vertex->pt.y < locMin1->vertex->pt.y;54else55return locMin2->vertex->pt.x > locMin1->vertex->pt.x;56}57};585960inline bool IsOdd(int val)61{62return (val & 1) ? true : false;63}646566inline bool IsHotEdge(const Active& e)67{68return (e.outrec);69}707172inline bool IsOpen(const Active& e)73{74return (e.local_min->is_open);75}767778inline bool IsOpenEnd(const Vertex& v)79{80return (v.flags & (VertexFlags::OpenStart | VertexFlags::OpenEnd)) !=81VertexFlags::Empty;82}838485inline bool IsOpenEnd(const Active& ae)86{87return IsOpenEnd(*ae.vertex_top);88}899091inline Active* GetPrevHotEdge(const Active& e)92{93Active* prev = e.prev_in_ael;94while (prev && (IsOpen(*prev) || !IsHotEdge(*prev)))95prev = prev->prev_in_ael;96return prev;97}9899inline bool IsFront(const Active& e)100{101return (&e == e.outrec->front_edge);102}103104inline bool IsInvalidPath(OutPt* op)105{106return (!op || op->next == op);107}108109/*******************************************************************************110* Dx: 0(90deg) *111* | *112* +inf (180deg) <--- o ---> -inf (0deg) *113*******************************************************************************/114115inline double GetDx(const Point64& pt1, const Point64& pt2)116{117double dy = double(pt2.y - pt1.y);118if (dy != 0)119return double(pt2.x - pt1.x) / dy;120else if (pt2.x > pt1.x)121return -std::numeric_limits<double>::max();122else123return std::numeric_limits<double>::max();124}125126inline int64_t TopX(const Active& ae, const int64_t currentY)127{128if ((currentY == ae.top.y) || (ae.top.x == ae.bot.x)) return ae.top.x;129else if (currentY == ae.bot.y) return ae.bot.x;130else return ae.bot.x + static_cast<int64_t>(nearbyint(ae.dx * (currentY - ae.bot.y)));131// nb: std::nearbyint (or std::round) substantially *improves* performance here132// as it greatly improves the likelihood of edge adjacency in ProcessIntersectList().133}134135136inline bool IsHorizontal(const Active& e)137{138return (e.top.y == e.bot.y);139}140141142inline bool IsHeadingRightHorz(const Active& e)143{144return e.dx == -std::numeric_limits<double>::max();145}146147148inline bool IsHeadingLeftHorz(const Active& e)149{150return e.dx == std::numeric_limits<double>::max();151}152153154inline void SwapActives(Active*& e1, Active*& e2)155{156Active* e = e1;157e1 = e2;158e2 = e;159}160161inline PathType GetPolyType(const Active& e)162{163return e.local_min->polytype;164}165166inline bool IsSamePolyType(const Active& e1, const Active& e2)167{168return e1.local_min->polytype == e2.local_min->polytype;169}170171inline void SetDx(Active& e)172{173e.dx = GetDx(e.bot, e.top);174}175176inline Vertex* NextVertex(const Active& e)177{178if (e.wind_dx > 0)179return e.vertex_top->next;180else181return e.vertex_top->prev;182}183184//PrevPrevVertex: useful to get the (inverted Y-axis) top of the185//alternate edge (ie left or right bound) during edge insertion.186inline Vertex* PrevPrevVertex(const Active& ae)187{188if (ae.wind_dx > 0)189return ae.vertex_top->prev->prev;190else191return ae.vertex_top->next->next;192}193194195inline Active* ExtractFromSEL(Active* ae)196{197Active* res = ae->next_in_sel;198if (res)199res->prev_in_sel = ae->prev_in_sel;200ae->prev_in_sel->next_in_sel = res;201return res;202}203204205inline void Insert1Before2InSEL(Active* ae1, Active* ae2)206{207ae1->prev_in_sel = ae2->prev_in_sel;208if (ae1->prev_in_sel)209ae1->prev_in_sel->next_in_sel = ae1;210ae1->next_in_sel = ae2;211ae2->prev_in_sel = ae1;212}213214inline bool IsMaxima(const Vertex& v)215{216return ((v.flags & VertexFlags::LocalMax) != VertexFlags::Empty);217}218219220inline bool IsMaxima(const Active& e)221{222return IsMaxima(*e.vertex_top);223}224225inline Vertex* GetCurrYMaximaVertex_Open(const Active& e)226{227Vertex* result = e.vertex_top;228if (e.wind_dx > 0)229while ((result->next->pt.y == result->pt.y) &&230((result->flags & (VertexFlags::OpenEnd |231VertexFlags::LocalMax)) == VertexFlags::Empty))232result = result->next;233else234while (result->prev->pt.y == result->pt.y &&235((result->flags & (VertexFlags::OpenEnd |236VertexFlags::LocalMax)) == VertexFlags::Empty))237result = result->prev;238if (!IsMaxima(*result)) result = nullptr; // not a maxima239return result;240}241242inline Vertex* GetCurrYMaximaVertex(const Active& e)243{244Vertex* result = e.vertex_top;245if (e.wind_dx > 0)246while (result->next->pt.y == result->pt.y) result = result->next;247else248while (result->prev->pt.y == result->pt.y) result = result->prev;249if (!IsMaxima(*result)) result = nullptr; // not a maxima250return result;251}252253Active* GetMaximaPair(const Active& e)254{255Active* e2;256e2 = e.next_in_ael;257while (e2)258{259if (e2->vertex_top == e.vertex_top) return e2; // Found!260e2 = e2->next_in_ael;261}262return nullptr;263}264265inline int PointCount(OutPt* op)266{267OutPt* op2 = op;268int cnt = 0;269do270{271op2 = op2->next;272++cnt;273} while (op2 != op);274return cnt;275}276277inline OutPt* DuplicateOp(OutPt* op, bool insert_after)278{279OutPt* result = new OutPt(op->pt, op->outrec);280if (insert_after)281{282result->next = op->next;283result->next->prev = result;284result->prev = op;285op->next = result;286}287else288{289result->prev = op->prev;290result->prev->next = result;291result->next = op;292op->prev = result;293}294return result;295}296297inline OutPt* DisposeOutPt(OutPt* op)298{299OutPt* result = op->next;300op->prev->next = op->next;301op->next->prev = op->prev;302delete op;303return result;304}305306307inline void DisposeOutPts(OutRec* outrec)308{309OutPt* op = outrec->pts;310op->prev->next = nullptr;311while (op)312{313OutPt* tmp = op;314op = op->next;315delete tmp;316};317outrec->pts = nullptr;318}319320321bool IntersectListSort(const IntersectNode& a, const IntersectNode& b)322{323//note different inequality tests ...324return (a.pt.y == b.pt.y) ? (a.pt.x < b.pt.x) : (a.pt.y > b.pt.y);325}326327328inline void SetSides(OutRec& outrec, Active& start_edge, Active& end_edge)329{330outrec.front_edge = &start_edge;331outrec.back_edge = &end_edge;332}333334335void SwapOutrecs(Active& e1, Active& e2)336{337OutRec* or1 = e1.outrec;338OutRec* or2 = e2.outrec;339if (or1 == or2)340{341Active* e = or1->front_edge;342or1->front_edge = or1->back_edge;343or1->back_edge = e;344return;345}346if (or1)347{348if (&e1 == or1->front_edge)349or1->front_edge = &e2;350else351or1->back_edge = &e2;352}353if (or2)354{355if (&e2 == or2->front_edge)356or2->front_edge = &e1;357else358or2->back_edge = &e1;359}360e1.outrec = or2;361e2.outrec = or1;362}363364365double Area(OutPt* op)366{367//https://en.wikipedia.org/wiki/Shoelace_formula368double result = 0.0;369OutPt* op2 = op;370do371{372result += static_cast<double>(op2->prev->pt.y + op2->pt.y) *373static_cast<double>(op2->prev->pt.x - op2->pt.x);374op2 = op2->next;375} while (op2 != op);376return result * 0.5;377}378379inline double AreaTriangle(const Point64& pt1,380const Point64& pt2, const Point64& pt3)381{382return (static_cast<double>(pt3.y + pt1.y) * static_cast<double>(pt3.x - pt1.x) +383static_cast<double>(pt1.y + pt2.y) * static_cast<double>(pt1.x - pt2.x) +384static_cast<double>(pt2.y + pt3.y) * static_cast<double>(pt2.x - pt3.x));385}386387void ReverseOutPts(OutPt* op)388{389if (!op) return;390391OutPt* op1 = op;392OutPt* op2;393394do395{396op2 = op1->next;397op1->next = op1->prev;398op1->prev = op2;399op1 = op2;400} while (op1 != op);401}402403inline void SwapSides(OutRec& outrec)404{405Active* e2 = outrec.front_edge;406outrec.front_edge = outrec.back_edge;407outrec.back_edge = e2;408outrec.pts = outrec.pts->next;409}410411inline OutRec* GetRealOutRec(OutRec* outrec)412{413while (outrec && !outrec->pts) outrec = outrec->owner;414return outrec;415}416417inline bool IsValidOwner(OutRec* outrec, OutRec* testOwner)418{419// prevent outrec owning itself either directly or indirectly420while (testOwner && testOwner != outrec) testOwner = testOwner->owner;421return !testOwner;422}423424inline void UncoupleOutRec(Active ae)425{426OutRec* outrec = ae.outrec;427if (!outrec) return;428outrec->front_edge->outrec = nullptr;429outrec->back_edge->outrec = nullptr;430outrec->front_edge = nullptr;431outrec->back_edge = nullptr;432}433434435inline bool PtsReallyClose(const Point64& pt1, const Point64& pt2)436{437return (std::llabs(pt1.x - pt2.x) < 2) && (std::llabs(pt1.y - pt2.y) < 2);438}439440inline bool IsVerySmallTriangle(const OutPt& op)441{442return op.next->next == op.prev &&443(PtsReallyClose(op.prev->pt, op.next->pt) ||444PtsReallyClose(op.pt, op.next->pt) ||445PtsReallyClose(op.pt, op.prev->pt));446}447448inline bool IsValidClosedPath(const OutPt* op)449{450return op && (op->next != op) && (op->next != op->prev) &&451!IsVerySmallTriangle(*op);452}453454inline bool OutrecIsAscending(const Active* hotEdge)455{456return (hotEdge == hotEdge->outrec->front_edge);457}458459inline void SwapFrontBackSides(OutRec& outrec)460{461Active* tmp = outrec.front_edge;462outrec.front_edge = outrec.back_edge;463outrec.back_edge = tmp;464outrec.pts = outrec.pts->next;465}466467inline bool EdgesAdjacentInAEL(const IntersectNode& inode)468{469return (inode.edge1->next_in_ael == inode.edge2) || (inode.edge1->prev_in_ael == inode.edge2);470}471472inline bool IsJoined(const Active& e)473{474return e.join_with != JoinWith::NoJoin;475}476477inline void SetOwner(OutRec* outrec, OutRec* new_owner)478{479//precondition1: new_owner is never null480new_owner->owner = GetRealOutRec(new_owner->owner);481OutRec* tmp = new_owner;482while (tmp && tmp != outrec) tmp = tmp->owner;483if (tmp) new_owner->owner = outrec->owner;484outrec->owner = new_owner;485}486487static PointInPolygonResult PointInOpPolygon(const Point64& pt, OutPt* op)488{489if (op == op->next || op->prev == op->next)490return PointInPolygonResult::IsOutside;491492OutPt* op2 = op;493do494{495if (op->pt.y != pt.y) break;496op = op->next;497} while (op != op2);498if (op->pt.y == pt.y) // not a proper polygon499return PointInPolygonResult::IsOutside;500501bool is_above = op->pt.y < pt.y, starting_above = is_above;502int val = 0;503op2 = op->next;504while (op2 != op)505{506if (is_above)507while (op2 != op && op2->pt.y < pt.y) op2 = op2->next;508else509while (op2 != op && op2->pt.y > pt.y) op2 = op2->next;510if (op2 == op) break;511512// must have touched or crossed the pt.Y horizontal513// and this must happen an even number of times514515if (op2->pt.y == pt.y) // touching the horizontal516{517if (op2->pt.x == pt.x || (op2->pt.y == op2->prev->pt.y &&518(pt.x < op2->prev->pt.x) != (pt.x < op2->pt.x)))519return PointInPolygonResult::IsOn;520521op2 = op2->next;522if (op2 == op) break;523continue;524}525526if (pt.x < op2->pt.x && pt.x < op2->prev->pt.x);527// do nothing because528// we're only interested in edges crossing on the left529else if ((pt.x > op2->prev->pt.x && pt.x > op2->pt.x))530val = 1 - val; // toggle val531else532{533int i = CrossProductSign(op2->prev->pt, op2->pt, pt);534if (i == 0) return PointInPolygonResult::IsOn;535if ((i < 0) == is_above) val = 1 - val;536}537is_above = !is_above;538op2 = op2->next;539}540541if (is_above != starting_above)542{543int i = CrossProductSign(op2->prev->pt, op2->pt, pt);544if (i == 0) return PointInPolygonResult::IsOn;545if ((i < 0) == is_above) val = 1 - val;546}547548if (val == 0) return PointInPolygonResult::IsOutside;549else return PointInPolygonResult::IsInside;550}551552inline Path64 GetCleanPath(OutPt* op)553{554Path64 result;555OutPt* op2 = op;556while (op2->next != op &&557((op2->pt.x == op2->next->pt.x && op2->pt.x == op2->prev->pt.x) ||558(op2->pt.y == op2->next->pt.y && op2->pt.y == op2->prev->pt.y))) op2 = op2->next;559result.emplace_back(op2->pt);560OutPt* prevOp = op2;561op2 = op2->next;562while (op2 != op)563{564if ((op2->pt.x != op2->next->pt.x || op2->pt.x != prevOp->pt.x) &&565(op2->pt.y != op2->next->pt.y || op2->pt.y != prevOp->pt.y))566{567result.emplace_back(op2->pt);568prevOp = op2;569}570op2 = op2->next;571}572return result;573}574575inline bool Path2ContainsPath1(OutPt* op1, OutPt* op2)576{577// this function accommodates rounding errors that578// can cause path micro intersections579PointInPolygonResult pip = PointInPolygonResult::IsOn;580OutPt* op = op1;581do {582switch (PointInOpPolygon(op->pt, op2))583{584case PointInPolygonResult::IsOutside:585if (pip == PointInPolygonResult::IsOutside) return false;586pip = PointInPolygonResult::IsOutside;587break;588case PointInPolygonResult::IsInside:589if (pip == PointInPolygonResult::IsInside) return true;590pip = PointInPolygonResult::IsInside;591break;592default: break;593}594op = op->next;595} while (op != op1);596// result unclear, so try again using cleaned paths597return Path2ContainsPath1(GetCleanPath(op1), GetCleanPath(op2)); // (#973)598}599600void AddLocMin(LocalMinimaList& list,601Vertex& vert, PathType polytype, bool is_open)602{603//make sure the vertex is added only once ...604if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::Empty) return;605606vert.flags = (vert.flags | VertexFlags::LocalMin);607list.emplace_back(std::make_unique <LocalMinima>(&vert, polytype, is_open));608}609610void AddPaths_(const Paths64& paths, PathType polytype, bool is_open,611std::vector<Vertex*>& vertexLists, LocalMinimaList& locMinList)612{613const auto total_vertex_count =614std::accumulate(paths.begin(), paths.end(), size_t(0),615[](const auto& a, const Path64& path)616{return a + path.size(); });617if (total_vertex_count == 0) return;618619Vertex* vertices = new Vertex[total_vertex_count], * v = vertices;620for (const Path64& path : paths)621{622//for each path create a circular double linked list of vertices623Vertex* v0 = v, * curr_v = v, * prev_v = nullptr;624625if (path.empty())626continue;627628v->prev = nullptr;629int cnt = 0;630for (const Point64& pt : path)631{632if (prev_v)633{634if (prev_v->pt == pt) continue; // ie skips duplicates635prev_v->next = curr_v;636}637curr_v->prev = prev_v;638curr_v->pt = pt;639curr_v->flags = VertexFlags::Empty;640prev_v = curr_v++;641cnt++;642}643if (!prev_v || !prev_v->prev) continue;644if (!is_open && prev_v->pt == v0->pt)645prev_v = prev_v->prev;646prev_v->next = v0;647v0->prev = prev_v;648v = curr_v; // ie get ready for next path649if (cnt < 2 || (cnt == 2 && !is_open)) continue;650651//now find and assign local minima652bool going_up, going_up0;653if (is_open)654{655curr_v = v0->next;656while (curr_v != v0 && curr_v->pt.y == v0->pt.y)657curr_v = curr_v->next;658going_up = curr_v->pt.y <= v0->pt.y;659if (going_up)660{661v0->flags = VertexFlags::OpenStart;662AddLocMin(locMinList , *v0, polytype, true);663}664else665v0->flags = VertexFlags::OpenStart | VertexFlags::LocalMax;666}667else // closed path668{669prev_v = v0->prev;670while (prev_v != v0 && prev_v->pt.y == v0->pt.y)671prev_v = prev_v->prev;672if (prev_v == v0)673continue; // only open paths can be completely flat674going_up = prev_v->pt.y > v0->pt.y;675}676677going_up0 = going_up;678prev_v = v0;679curr_v = v0->next;680while (curr_v != v0)681{682if (curr_v->pt.y > prev_v->pt.y && going_up)683{684prev_v->flags = (prev_v->flags | VertexFlags::LocalMax);685going_up = false;686}687else if (curr_v->pt.y < prev_v->pt.y && !going_up)688{689going_up = true;690AddLocMin(locMinList, *prev_v, polytype, is_open);691}692prev_v = curr_v;693curr_v = curr_v->next;694}695696if (is_open)697{698prev_v->flags = prev_v->flags | VertexFlags::OpenEnd;699if (going_up)700prev_v->flags = prev_v->flags | VertexFlags::LocalMax;701else702AddLocMin(locMinList, *prev_v, polytype, is_open);703}704else if (going_up != going_up0)705{706if (going_up0) AddLocMin(locMinList, *prev_v, polytype, false);707else prev_v->flags = prev_v->flags | VertexFlags::LocalMax;708}709} // end processing current path710711vertexLists.emplace_back(vertices);712}713714//------------------------------------------------------------------------------715// ReuseableDataContainer64 methods ...716//------------------------------------------------------------------------------717718void ReuseableDataContainer64::AddLocMin(Vertex& vert, PathType polytype, bool is_open)719{720//make sure the vertex is added only once ...721if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::Empty) return;722723vert.flags = (vert.flags | VertexFlags::LocalMin);724minima_list_.emplace_back(std::make_unique <LocalMinima>(&vert, polytype, is_open));725}726727void ReuseableDataContainer64::AddPaths(const Paths64& paths,728PathType polytype, bool is_open)729{730AddPaths_(paths, polytype, is_open, vertex_lists_, minima_list_);731}732733ReuseableDataContainer64::~ReuseableDataContainer64()734{735Clear();736}737738void ReuseableDataContainer64::Clear()739{740minima_list_.clear();741for (auto v : vertex_lists_) delete[] v;742vertex_lists_.clear();743}744745//------------------------------------------------------------------------------746// ClipperBase methods ...747//------------------------------------------------------------------------------748749ClipperBase::~ClipperBase()750{751Clear();752}753754void ClipperBase::DeleteEdges(Active*& e)755{756while (e)757{758Active* e2 = e;759e = e->next_in_ael;760delete e2;761}762}763764void ClipperBase::CleanUp()765{766DeleteEdges(actives_);767scanline_list_ = std::priority_queue<int64_t>();768intersect_nodes_.clear();769DisposeAllOutRecs();770horz_seg_list_.clear();771horz_join_list_.clear();772}773774775void ClipperBase::Clear()776{777CleanUp();778DisposeVerticesAndLocalMinima();779current_locmin_iter_ = minima_list_.begin();780minima_list_sorted_ = false;781has_open_paths_ = false;782}783784785void ClipperBase::Reset()786{787if (!minima_list_sorted_)788{789std::stable_sort(minima_list_.begin(), minima_list_.end(), LocMinSorter()); //#594790minima_list_sorted_ = true;791}792LocalMinimaList::const_reverse_iterator i;793for (i = minima_list_.rbegin(); i != minima_list_.rend(); ++i)794InsertScanline((*i)->vertex->pt.y);795796current_locmin_iter_ = minima_list_.begin();797actives_ = nullptr;798sel_ = nullptr;799succeeded_ = true;800}801802803#ifdef USINGZ804void ClipperBase::SetZ(const Active& e1, const Active& e2, Point64& ip)805{806if (!zCallback_) return;807// prioritize subject over clip vertices by passing808// subject vertices before clip vertices in the callback809if (GetPolyType(e1) == PathType::Subject)810{811if (ip == e1.bot) ip.z = e1.bot.z;812else if (ip == e1.top) ip.z = e1.top.z;813else if (ip == e2.bot) ip.z = e2.bot.z;814else if (ip == e2.top) ip.z = e2.top.z;815else ip.z = DefaultZ;816zCallback_(e1.bot, e1.top, e2.bot, e2.top, ip);817}818else819{820if (ip == e2.bot) ip.z = e2.bot.z;821else if (ip == e2.top) ip.z = e2.top.z;822else if (ip == e1.bot) ip.z = e1.bot.z;823else if (ip == e1.top) ip.z = e1.top.z;824else ip.z = DefaultZ;825zCallback_(e2.bot, e2.top, e1.bot, e1.top, ip);826}827}828#endif829830void ClipperBase::AddPath(const Path64& path, PathType polytype, bool is_open)831{832AddPaths(Paths64(1, path), polytype, is_open);833}834835void ClipperBase::AddPaths(const Paths64& paths, PathType polytype, bool is_open)836{837if (is_open) has_open_paths_ = true;838minima_list_sorted_ = false;839AddPaths_(paths, polytype, is_open, vertex_lists_, minima_list_);840}841842void ClipperBase::AddReuseableData(const ReuseableDataContainer64& reuseable_data)843{844// nb: reuseable_data will continue to own the vertices845// and remains responsible for their clean up.846succeeded_ = false;847minima_list_sorted_ = false;848LocalMinimaList::const_iterator i;849for (i = reuseable_data.minima_list_.cbegin(); i != reuseable_data.minima_list_.cend(); ++i)850{851minima_list_.emplace_back(std::make_unique <LocalMinima>((*i)->vertex, (*i)->polytype, (*i)->is_open));852if ((*i)->is_open) has_open_paths_ = true;853}854}855856void ClipperBase::InsertScanline(int64_t y)857{858scanline_list_.push(y);859}860861862bool ClipperBase::PopScanline(int64_t& y)863{864if (scanline_list_.empty()) return false;865y = scanline_list_.top();866scanline_list_.pop();867while (!scanline_list_.empty() && y == scanline_list_.top())868scanline_list_.pop(); // Pop duplicates.869return true;870}871872873bool ClipperBase::PopLocalMinima(int64_t y, LocalMinima*& local_minima)874{875if (current_locmin_iter_ == minima_list_.end() || (*current_locmin_iter_)->vertex->pt.y != y) return false;876local_minima = (current_locmin_iter_++)->get();877return true;878}879880void ClipperBase::DisposeAllOutRecs()881{882for (auto outrec : outrec_list_)883{884if (outrec->pts) DisposeOutPts(outrec);885delete outrec;886}887outrec_list_.resize(0);888}889890void ClipperBase::DisposeVerticesAndLocalMinima()891{892minima_list_.clear();893for (auto v : vertex_lists_) delete[] v;894vertex_lists_.clear();895}896897898void ClipperBase::AddLocMin(Vertex& vert, PathType polytype, bool is_open)899{900//make sure the vertex is added only once ...901if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::Empty) return;902903vert.flags = (vert.flags | VertexFlags::LocalMin);904minima_list_.emplace_back(std::make_unique <LocalMinima>(&vert, polytype, is_open));905}906907bool ClipperBase::IsContributingClosed(const Active& e) const908{909switch (fillrule_)910{911case FillRule::EvenOdd:912break;913case FillRule::NonZero:914if (abs(e.wind_cnt) != 1) return false;915break;916case FillRule::Positive:917if (e.wind_cnt != 1) return false;918break;919case FillRule::Negative:920if (e.wind_cnt != -1) return false;921break;922// Should never happen, but adding this to stop a compiler warning923default:924break;925}926927switch (cliptype_)928{929case ClipType::NoClip:930return false;931case ClipType::Intersection:932switch (fillrule_)933{934case FillRule::Positive:935return (e.wind_cnt2 > 0);936case FillRule::Negative:937return (e.wind_cnt2 < 0);938default:939return (e.wind_cnt2 != 0);940}941break;942943case ClipType::Union:944switch (fillrule_)945{946case FillRule::Positive:947return (e.wind_cnt2 <= 0);948case FillRule::Negative:949return (e.wind_cnt2 >= 0);950default:951return (e.wind_cnt2 == 0);952}953break;954955case ClipType::Difference:956bool result;957switch (fillrule_)958{959case FillRule::Positive:960result = (e.wind_cnt2 <= 0);961break;962case FillRule::Negative:963result = (e.wind_cnt2 >= 0);964break;965default:966result = (e.wind_cnt2 == 0);967}968if (GetPolyType(e) == PathType::Subject)969return result;970else971return !result;972break;973974case ClipType::Xor: return true; break;975// Should never happen, but adding this to stop a compiler warning976default:977break;978}979return false; // we should never get here980}981982983inline bool ClipperBase::IsContributingOpen(const Active& e) const984{985bool is_in_clip, is_in_subj;986switch (fillrule_)987{988case FillRule::Positive:989is_in_clip = e.wind_cnt2 > 0;990is_in_subj = e.wind_cnt > 0;991break;992case FillRule::Negative:993is_in_clip = e.wind_cnt2 < 0;994is_in_subj = e.wind_cnt < 0;995break;996default:997is_in_clip = e.wind_cnt2 != 0;998is_in_subj = e.wind_cnt != 0;999}10001001switch (cliptype_)1002{1003case ClipType::Intersection: return is_in_clip;1004case ClipType::Union: return (!is_in_subj && !is_in_clip);1005default: return !is_in_clip;1006}1007}100810091010void ClipperBase::SetWindCountForClosedPathEdge(Active& e)1011{1012//Wind counts refer to polygon regions not edges, so here an edge's WindCnt1013//indicates the higher of the wind counts for the two regions touching the1014//edge. (NB Adjacent regions can only ever have their wind counts differ by1015//one. Also, open paths have no meaningful wind directions or counts.)10161017Active* e2 = e.prev_in_ael;1018//find the nearest closed path edge of the same PolyType in AEL (heading left)1019PathType pt = GetPolyType(e);1020while (e2 && (GetPolyType(*e2) != pt || IsOpen(*e2))) e2 = e2->prev_in_ael;10211022if (!e2)1023{1024e.wind_cnt = e.wind_dx;1025e2 = actives_;1026}1027else if (fillrule_ == FillRule::EvenOdd)1028{1029e.wind_cnt = e.wind_dx;1030e.wind_cnt2 = e2->wind_cnt2;1031e2 = e2->next_in_ael;1032}1033else1034{1035//NonZero, positive, or negative filling here ...1036//if e's WindCnt is in the SAME direction as its WindDx, then polygon1037//filling will be on the right of 'e'.1038//NB neither e2.WindCnt nor e2.WindDx should ever be 0.1039if (e2->wind_cnt * e2->wind_dx < 0)1040{1041//opposite directions so 'e' is outside 'e2' ...1042if (abs(e2->wind_cnt) > 1)1043{1044//outside prev poly but still inside another.1045if (e2->wind_dx * e.wind_dx < 0)1046//reversing direction so use the same WC1047e.wind_cnt = e2->wind_cnt;1048else1049//otherwise keep 'reducing' the WC by 1 (ie towards 0) ...1050e.wind_cnt = e2->wind_cnt + e.wind_dx;1051}1052else1053//now outside all polys of same polytype so set own WC ...1054e.wind_cnt = (IsOpen(e) ? 1 : e.wind_dx);1055}1056else1057{1058//'e' must be inside 'e2'1059if (e2->wind_dx * e.wind_dx < 0)1060//reversing direction so use the same WC1061e.wind_cnt = e2->wind_cnt;1062else1063//otherwise keep 'increasing' the WC by 1 (ie away from 0) ...1064e.wind_cnt = e2->wind_cnt + e.wind_dx;1065}1066e.wind_cnt2 = e2->wind_cnt2;1067e2 = e2->next_in_ael; // ie get ready to calc WindCnt21068}10691070//update wind_cnt2 ...1071if (fillrule_ == FillRule::EvenOdd)1072while (e2 != &e)1073{1074if (GetPolyType(*e2) != pt && !IsOpen(*e2))1075e.wind_cnt2 = (e.wind_cnt2 == 0 ? 1 : 0);1076e2 = e2->next_in_ael;1077}1078else1079while (e2 != &e)1080{1081if (GetPolyType(*e2) != pt && !IsOpen(*e2))1082e.wind_cnt2 += e2->wind_dx;1083e2 = e2->next_in_ael;1084}1085}108610871088void ClipperBase::SetWindCountForOpenPathEdge(Active& e)1089{1090Active* e2 = actives_;1091if (fillrule_ == FillRule::EvenOdd)1092{1093int cnt1 = 0, cnt2 = 0;1094while (e2 != &e)1095{1096if (GetPolyType(*e2) == PathType::Clip)1097cnt2++;1098else if (!IsOpen(*e2))1099cnt1++;1100e2 = e2->next_in_ael;1101}1102e.wind_cnt = (IsOdd(cnt1) ? 1 : 0);1103e.wind_cnt2 = (IsOdd(cnt2) ? 1 : 0);1104}1105else1106{1107while (e2 != &e)1108{1109if (GetPolyType(*e2) == PathType::Clip)1110e.wind_cnt2 += e2->wind_dx;1111else if (!IsOpen(*e2))1112e.wind_cnt += e2->wind_dx;1113e2 = e2->next_in_ael;1114}1115}1116}11171118bool IsValidAelOrder(const Active& resident, const Active& newcomer)1119{1120if (newcomer.curr_x != resident.curr_x)1121return newcomer.curr_x > resident.curr_x;11221123//get the turning direction a1.top, a2.bot, a2.top1124int i = CrossProductSign(resident.top, newcomer.bot, newcomer.top);1125if (i != 0) return i < 0;11261127//edges must be collinear to get here1128//for starting open paths, place them according to1129//the direction they're about to turn1130if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y))1131{1132return (CrossProductSign(newcomer.bot, resident.top, NextVertex(resident)->pt) <= 0);1133}1134else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y))1135{1136return (CrossProductSign(newcomer.bot, newcomer.top, NextVertex(newcomer)->pt) >= 0);1137}11381139int64_t y = newcomer.bot.y;1140bool newcomerIsLeft = newcomer.is_left_bound;11411142if (resident.bot.y != y || resident.local_min->vertex->pt.y != y)1143return newcomer.is_left_bound;1144//resident must also have just been inserted1145else if (resident.is_left_bound != newcomerIsLeft)1146return newcomerIsLeft;1147else if (IsCollinear(PrevPrevVertex(resident)->pt,1148resident.bot, resident.top)) return true;1149else1150//compare turning direction of the alternate bound1151return (CrossProductSign(PrevPrevVertex(resident)->pt,1152newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft;1153}115411551156void ClipperBase::InsertLeftEdge(Active& e)1157{1158Active* e2;1159if (!actives_)1160{1161e.prev_in_ael = nullptr;1162e.next_in_ael = nullptr;1163actives_ = &e;1164}1165else if (!IsValidAelOrder(*actives_, e))1166{1167e.prev_in_ael = nullptr;1168e.next_in_ael = actives_;1169actives_->prev_in_ael = &e;1170actives_ = &e;1171}1172else1173{1174e2 = actives_;1175while (e2->next_in_ael && IsValidAelOrder(*e2->next_in_ael, e))1176e2 = e2->next_in_ael;1177if (e2->join_with == JoinWith::Right)1178e2 = e2->next_in_ael;1179if (!e2) return; // should never happen and stops compiler warning :)1180e.next_in_ael = e2->next_in_ael;1181if (e2->next_in_ael) e2->next_in_ael->prev_in_ael = &e;1182e.prev_in_ael = e2;1183e2->next_in_ael = &e;1184}1185}118611871188void InsertRightEdge(Active& e, Active& e2)1189{1190e2.next_in_ael = e.next_in_ael;1191if (e.next_in_ael) e.next_in_ael->prev_in_ael = &e2;1192e2.prev_in_ael = &e;1193e.next_in_ael = &e2;1194}119511961197void ClipperBase::InsertLocalMinimaIntoAEL(int64_t bot_y)1198{1199LocalMinima* local_minima;1200Active* left_bound, * right_bound;1201//Add any local minima (if any) at BotY ...1202//nb: horizontal local minima edges should contain locMin.vertex.prev12031204while (PopLocalMinima(bot_y, local_minima))1205{1206if ((local_minima->vertex->flags & VertexFlags::OpenStart) != VertexFlags::Empty)1207{1208left_bound = nullptr;1209}1210else1211{1212left_bound = new Active();1213left_bound->bot = local_minima->vertex->pt;1214left_bound->curr_x = left_bound->bot.x;1215left_bound->wind_dx = -1;1216left_bound->vertex_top = local_minima->vertex->prev; // ie descending1217left_bound->top = left_bound->vertex_top->pt;1218left_bound->local_min = local_minima;1219SetDx(*left_bound);1220}12211222if ((local_minima->vertex->flags & VertexFlags::OpenEnd) != VertexFlags::Empty)1223{1224right_bound = nullptr;1225}1226else1227{1228right_bound = new Active();1229right_bound->bot = local_minima->vertex->pt;1230right_bound->curr_x = right_bound->bot.x;1231right_bound->wind_dx = 1;1232right_bound->vertex_top = local_minima->vertex->next; // ie ascending1233right_bound->top = right_bound->vertex_top->pt;1234right_bound->local_min = local_minima;1235SetDx(*right_bound);1236}12371238//Currently LeftB is just the descending bound and RightB is the ascending.1239//Now if the LeftB isn't on the left of RightB then we need swap them.1240if (left_bound && right_bound)1241{1242if (IsHorizontal(*left_bound))1243{1244if (IsHeadingRightHorz(*left_bound)) SwapActives(left_bound, right_bound);1245}1246else if (IsHorizontal(*right_bound))1247{1248if (IsHeadingLeftHorz(*right_bound)) SwapActives(left_bound, right_bound);1249}1250else if (left_bound->dx < right_bound->dx)1251SwapActives(left_bound, right_bound);1252}1253else if (!left_bound)1254{1255left_bound = right_bound;1256right_bound = nullptr;1257}12581259bool contributing;1260left_bound->is_left_bound = true;1261InsertLeftEdge(*left_bound);12621263if (IsOpen(*left_bound))1264{1265SetWindCountForOpenPathEdge(*left_bound);1266contributing = IsContributingOpen(*left_bound);1267}1268else1269{1270SetWindCountForClosedPathEdge(*left_bound);1271contributing = IsContributingClosed(*left_bound);1272}12731274if (right_bound)1275{1276right_bound->is_left_bound = false;1277right_bound->wind_cnt = left_bound->wind_cnt;1278right_bound->wind_cnt2 = left_bound->wind_cnt2;1279InsertRightEdge(*left_bound, *right_bound); ///////1280if (contributing)1281{1282AddLocalMinPoly(*left_bound, *right_bound, left_bound->bot, true);1283if (!IsHorizontal(*left_bound))1284CheckJoinLeft(*left_bound, left_bound->bot);1285}12861287while (right_bound->next_in_ael &&1288IsValidAelOrder(*right_bound->next_in_ael, *right_bound))1289{1290IntersectEdges(*right_bound, *right_bound->next_in_ael, right_bound->bot);1291SwapPositionsInAEL(*right_bound, *right_bound->next_in_ael);1292}12931294if (IsHorizontal(*right_bound))1295PushHorz(*right_bound);1296else1297{1298CheckJoinRight(*right_bound, right_bound->bot);1299InsertScanline(right_bound->top.y);1300}1301}1302else if (contributing)1303{1304StartOpenPath(*left_bound, left_bound->bot);1305}13061307if (IsHorizontal(*left_bound))1308PushHorz(*left_bound);1309else1310InsertScanline(left_bound->top.y);1311} // while (PopLocalMinima())1312}131313141315inline void ClipperBase::PushHorz(Active& e)1316{1317e.next_in_sel = (sel_ ? sel_ : nullptr);1318sel_ = &e;1319}132013211322inline bool ClipperBase::PopHorz(Active*& e)1323{1324e = sel_;1325if (!e) return false;1326sel_ = sel_->next_in_sel;1327return true;1328}132913301331OutPt* ClipperBase::AddLocalMinPoly(Active& e1, Active& e2,1332const Point64& pt, bool is_new)1333{1334OutRec* outrec = NewOutRec();1335e1.outrec = outrec;1336e2.outrec = outrec;13371338if (IsOpen(e1))1339{1340outrec->owner = nullptr;1341outrec->is_open = true;1342if (e1.wind_dx > 0)1343SetSides(*outrec, e1, e2);1344else1345SetSides(*outrec, e2, e1);1346}1347else1348{1349Active* prevHotEdge = GetPrevHotEdge(e1);1350//e.windDx is the winding direction of the **input** paths1351//and unrelated to the winding direction of output polygons.1352//Output orientation is determined by e.outrec.frontE which is1353//the ascending edge (see AddLocalMinPoly).1354if (prevHotEdge)1355{1356if (using_polytree_)1357SetOwner(outrec, prevHotEdge->outrec);1358if (OutrecIsAscending(prevHotEdge) == is_new)1359SetSides(*outrec, e2, e1);1360else1361SetSides(*outrec, e1, e2);1362}1363else1364{1365outrec->owner = nullptr;1366if (is_new)1367SetSides(*outrec, e1, e2);1368else1369SetSides(*outrec, e2, e1);1370}1371}13721373OutPt* op = new OutPt(pt, outrec);1374outrec->pts = op;1375return op;1376}137713781379OutPt* ClipperBase::AddLocalMaxPoly(Active& e1, Active& e2, const Point64& pt)1380{1381if (IsJoined(e1)) Split(e1, pt);1382if (IsJoined(e2)) Split(e2, pt);13831384if (IsFront(e1) == IsFront(e2))1385{1386if (IsOpenEnd(e1))1387SwapFrontBackSides(*e1.outrec);1388else if (IsOpenEnd(e2))1389SwapFrontBackSides(*e2.outrec);1390else1391{1392succeeded_ = false;1393return nullptr;1394}1395}13961397OutPt* result = AddOutPt(e1, pt);1398if (e1.outrec == e2.outrec)1399{1400OutRec& outrec = *e1.outrec;1401outrec.pts = result;14021403if (using_polytree_)1404{1405Active* e = GetPrevHotEdge(e1);1406if (!e)1407outrec.owner = nullptr;1408else1409SetOwner(&outrec, e->outrec);1410// nb: outRec.owner here is likely NOT the real1411// owner but this will be checked in RecursiveCheckOwners()1412}14131414UncoupleOutRec(e1);1415result = outrec.pts;1416if (outrec.owner && !outrec.owner->front_edge)1417outrec.owner = GetRealOutRec(outrec.owner);1418}1419//and to preserve the winding orientation of outrec ...1420else if (IsOpen(e1))1421{1422if (e1.wind_dx < 0)1423JoinOutrecPaths(e1, e2);1424else1425JoinOutrecPaths(e2, e1);1426}1427else if (e1.outrec->idx < e2.outrec->idx)1428JoinOutrecPaths(e1, e2);1429else1430JoinOutrecPaths(e2, e1);1431return result;1432}14331434void ClipperBase::JoinOutrecPaths(Active& e1, Active& e2)1435{1436//join e2 outrec path onto e1 outrec path and then delete e2 outrec path1437//pointers. (NB Only very rarely do the joining ends share the same coords.)1438OutPt* p1_st = e1.outrec->pts;1439OutPt* p2_st = e2.outrec->pts;1440OutPt* p1_end = p1_st->next;1441OutPt* p2_end = p2_st->next;1442if (IsFront(e1))1443{1444p2_end->prev = p1_st;1445p1_st->next = p2_end;1446p2_st->next = p1_end;1447p1_end->prev = p2_st;1448e1.outrec->pts = p2_st;1449e1.outrec->front_edge = e2.outrec->front_edge;1450if (e1.outrec->front_edge)1451e1.outrec->front_edge->outrec = e1.outrec;1452}1453else1454{1455p1_end->prev = p2_st;1456p2_st->next = p1_end;1457p1_st->next = p2_end;1458p2_end->prev = p1_st;1459e1.outrec->back_edge = e2.outrec->back_edge;1460if (e1.outrec->back_edge)1461e1.outrec->back_edge->outrec = e1.outrec;1462}14631464//after joining, the e2.OutRec must contains no vertices ...1465e2.outrec->front_edge = nullptr;1466e2.outrec->back_edge = nullptr;1467e2.outrec->pts = nullptr;14681469if (IsOpenEnd(e1))1470{1471e2.outrec->pts = e1.outrec->pts;1472e1.outrec->pts = nullptr;1473}1474else1475SetOwner(e2.outrec, e1.outrec);14761477//and e1 and e2 are maxima and are about to be dropped from the Actives list.1478e1.outrec = nullptr;1479e2.outrec = nullptr;1480}14811482OutRec* ClipperBase::NewOutRec()1483{1484OutRec* result = new OutRec();1485result->idx = outrec_list_.size();1486outrec_list_.emplace_back(result);1487result->pts = nullptr;1488result->owner = nullptr;1489result->polypath = nullptr;1490result->is_open = false;1491result->splits = nullptr;1492return result;1493}149414951496OutPt* ClipperBase::AddOutPt(const Active& e, const Point64& pt)1497{1498OutPt* new_op = nullptr;14991500//Outrec.OutPts: a circular doubly-linked-list of POutPt where ...1501//op_front[.Prev]* ~~~> op_back & op_back == op_front.Next1502OutRec* outrec = e.outrec;1503bool to_front = IsFront(e);1504OutPt* op_front = outrec->pts;1505OutPt* op_back = op_front->next;15061507if (to_front)1508{1509if (pt == op_front->pt)1510return op_front;1511}1512else if (pt == op_back->pt)1513return op_back;15141515new_op = new OutPt(pt, outrec);1516op_back->prev = new_op;1517new_op->prev = op_front;1518new_op->next = op_back;1519op_front->next = new_op;1520if (to_front) outrec->pts = new_op;1521return new_op;1522}15231524void ClipperBase::CleanCollinear(OutRec* outrec)1525{1526outrec = GetRealOutRec(outrec);1527if (!outrec || outrec->is_open) return;1528if (!IsValidClosedPath(outrec->pts))1529{1530DisposeOutPts(outrec);1531return;1532}15331534OutPt* startOp = outrec->pts, * op2 = startOp;1535for (; ; )1536{1537//NB if preserveCollinear == true, then only remove 180 deg. spikes1538if (IsCollinear(op2->prev->pt, op2->pt, op2->next->pt) &&1539(op2->pt == op2->prev->pt ||1540op2->pt == op2->next->pt || !preserve_collinear_ ||1541DotProduct(op2->prev->pt, op2->pt, op2->next->pt) < 0))1542{15431544if (op2 == outrec->pts) outrec->pts = op2->prev;15451546op2 = DisposeOutPt(op2);1547if (!IsValidClosedPath(op2))1548{1549DisposeOutPts(outrec);1550return;1551}1552startOp = op2;1553continue;1554}1555op2 = op2->next;1556if (op2 == startOp) break;1557}1558FixSelfIntersects(outrec);1559}15601561void ClipperBase::DoSplitOp (OutRec* outrec, OutPt* splitOp)1562{1563// splitOp.prev -> splitOp &&1564// splitOp.next -> splitOp.next.next are intersecting1565OutPt* prevOp = splitOp->prev;1566OutPt* nextNextOp = splitOp->next->next;1567outrec->pts = prevOp;15681569Point64 ip;1570GetSegmentIntersectPt(prevOp->pt, splitOp->pt,1571splitOp->next->pt, nextNextOp->pt, ip);15721573#ifdef USINGZ1574if (zCallback_) zCallback_(prevOp->pt, splitOp->pt,1575splitOp->next->pt, nextNextOp->pt, ip);1576#endif1577double area1 = Area(outrec->pts);1578double absArea1 = std::fabs(area1);1579if (absArea1 < 2)1580{1581DisposeOutPts(outrec);1582return;1583}15841585double area2 = AreaTriangle(ip, splitOp->pt, splitOp->next->pt);1586double absArea2 = std::fabs(area2);15871588// de-link splitOp and splitOp.next from the path1589// while inserting the intersection point1590if (ip == prevOp->pt || ip == nextNextOp->pt)1591{1592nextNextOp->prev = prevOp;1593prevOp->next = nextNextOp;1594}1595else1596{1597OutPt* newOp2 = new OutPt(ip, prevOp->outrec);1598newOp2->prev = prevOp;1599newOp2->next = nextNextOp;1600nextNextOp->prev = newOp2;1601prevOp->next = newOp2;1602}16031604// area1 is the path's area *before* splitting, whereas area2 is1605// the area of the triangle containing splitOp & splitOp.next.1606// So the only way for these areas to have the same sign is if1607// the split triangle is larger than the path containing prevOp or1608// if there's more than one self-intersection.1609if (absArea2 >= 1 &&1610(absArea2 > absArea1 || (area2 > 0) == (area1 > 0)))1611{1612OutRec* newOr = NewOutRec();1613newOr->owner = outrec->owner;16141615splitOp->outrec = newOr;1616splitOp->next->outrec = newOr;1617OutPt* newOp = new OutPt(ip, newOr);1618newOp->prev = splitOp->next;1619newOp->next = splitOp;1620newOr->pts = newOp;1621splitOp->prev = newOp;1622splitOp->next->next = newOp;16231624if (using_polytree_)1625{1626if (Path2ContainsPath1(prevOp, newOp))1627{1628newOr->splits = new OutRecList();1629newOr->splits->emplace_back(outrec);1630}1631else1632{1633if (!outrec->splits) outrec->splits = new OutRecList();1634outrec->splits->emplace_back(newOr);1635}1636}1637}1638else1639{1640delete splitOp->next;1641delete splitOp;1642}1643}16441645void ClipperBase::FixSelfIntersects(OutRec* outrec)1646{1647OutPt* op2 = outrec->pts;1648if (op2->prev == op2->next->next)1649return; // because triangles can't self-intersect1650for (; ; )1651{1652if (SegmentsIntersect(op2->prev->pt,1653op2->pt, op2->next->pt, op2->next->next->pt))1654{1655if (SegmentsIntersect(op2->prev->pt,1656op2->pt, op2->next->next->pt, op2->next->next->next->pt))1657{1658// adjacent intersections (ie a micro self-intersections)1659op2 = DuplicateOp(op2, false);1660op2->pt = op2->next->next->next->pt;1661op2 = op2->next;1662}1663else1664{1665if (op2 == outrec->pts || op2->next == outrec->pts)1666outrec->pts = outrec->pts->prev;1667DoSplitOp(outrec, op2);1668if (!outrec->pts) break;1669op2 = outrec->pts;1670if (op2->prev == op2->next->next)1671break; // again, because triangles can't self-intersect1672continue;1673}1674}1675else1676op2 = op2->next;16771678if (op2 == outrec->pts) break;1679}1680}168116821683inline void UpdateOutrecOwner(OutRec* outrec)1684{1685OutPt* opCurr = outrec->pts;1686for (; ; )1687{1688opCurr->outrec = outrec;1689opCurr = opCurr->next;1690if (opCurr == outrec->pts) return;1691}1692}169316941695OutPt* ClipperBase::StartOpenPath(Active& e, const Point64& pt)1696{1697OutRec* outrec = NewOutRec();1698outrec->is_open = true;16991700if (e.wind_dx > 0)1701{1702outrec->front_edge = &e;1703outrec->back_edge = nullptr;1704}1705else1706{1707outrec->front_edge = nullptr;1708outrec->back_edge = &e;1709}17101711e.outrec = outrec;17121713OutPt* op = new OutPt(pt, outrec);1714outrec->pts = op;1715return op;1716}17171718inline void TrimHorz(Active& horzEdge, bool preserveCollinear)1719{1720bool wasTrimmed = false;1721Point64 pt = NextVertex(horzEdge)->pt;1722while (pt.y == horzEdge.top.y)1723{1724//always trim 180 deg. spikes (in closed paths)1725//but otherwise break if preserveCollinear = true1726if (preserveCollinear &&1727((pt.x < horzEdge.top.x) != (horzEdge.bot.x < horzEdge.top.x)))1728break;17291730horzEdge.vertex_top = NextVertex(horzEdge);1731horzEdge.top = pt;1732wasTrimmed = true;1733if (IsMaxima(horzEdge)) break;1734pt = NextVertex(horzEdge)->pt;1735}17361737if (wasTrimmed) SetDx(horzEdge); // +/-infinity1738}173917401741inline void ClipperBase::UpdateEdgeIntoAEL(Active* e)1742{1743e->bot = e->top;1744e->vertex_top = NextVertex(*e);1745e->top = e->vertex_top->pt;1746e->curr_x = e->bot.x;1747SetDx(*e);17481749if (IsJoined(*e)) Split(*e, e->bot);17501751if (IsHorizontal(*e))1752{1753if (!IsOpen(*e)) TrimHorz(*e, preserve_collinear_);1754return;1755}17561757InsertScanline(e->top.y);1758CheckJoinLeft(*e, e->bot);1759CheckJoinRight(*e, e->bot, true); // (#500)1760}17611762Active* FindEdgeWithMatchingLocMin(Active* e)1763{1764Active* result = e->next_in_ael;1765while (result)1766{1767if (result->local_min == e->local_min) return result;1768else if (!IsHorizontal(*result) && e->bot != result->bot) result = nullptr;1769else result = result->next_in_ael;1770}1771result = e->prev_in_ael;1772while (result)1773{1774if (result->local_min == e->local_min) return result;1775else if (!IsHorizontal(*result) && e->bot != result->bot) return nullptr;1776else result = result->prev_in_ael;1777}1778return result;1779}178017811782void ClipperBase::IntersectEdges(Active& e1, Active& e2, const Point64& pt)1783{1784//MANAGE OPEN PATH INTERSECTIONS SEPARATELY ...1785if (has_open_paths_ && (IsOpen(e1) || IsOpen(e2)))1786{1787if (IsOpen(e1) && IsOpen(e2)) return;1788Active* edge_o, * edge_c;1789if (IsOpen(e1))1790{1791edge_o = &e1;1792edge_c = &e2;1793}1794else1795{1796edge_o = &e2;1797edge_c = &e1;1798}1799if (IsJoined(*edge_c)) Split(*edge_c, pt); // needed for safety18001801if (abs(edge_c->wind_cnt) != 1) return;1802switch (cliptype_)1803{1804case ClipType::Union:1805if (!IsHotEdge(*edge_c)) return;1806break;1807default:1808if (edge_c->local_min->polytype == PathType::Subject)1809return;1810}18111812switch (fillrule_)1813{1814case FillRule::Positive:1815if (edge_c->wind_cnt != 1) return;1816break;1817case FillRule::Negative:1818if (edge_c->wind_cnt != -1) return;1819break;1820default:1821if (std::abs(edge_c->wind_cnt) != 1) return;1822}18231824#ifdef USINGZ1825OutPt* resultOp;1826#endif1827//toggle contribution ...1828if (IsHotEdge(*edge_o))1829{1830#ifdef USINGZ1831resultOp = AddOutPt(*edge_o, pt);1832#else1833AddOutPt(*edge_o, pt);1834#endif1835if (IsFront(*edge_o)) edge_o->outrec->front_edge = nullptr;1836else edge_o->outrec->back_edge = nullptr;1837edge_o->outrec = nullptr;1838}18391840//horizontal edges can pass under open paths at a LocMins1841else if (pt == edge_o->local_min->vertex->pt &&1842!IsOpenEnd(*edge_o->local_min->vertex))1843{1844//find the other side of the LocMin and1845//if it's 'hot' join up with it ...1846Active* e3 = FindEdgeWithMatchingLocMin(edge_o);1847if (e3 && IsHotEdge(*e3))1848{1849edge_o->outrec = e3->outrec;1850if (edge_o->wind_dx > 0)1851SetSides(*e3->outrec, *edge_o, *e3);1852else1853SetSides(*e3->outrec, *e3, *edge_o);1854return;1855}1856else1857#ifdef USINGZ1858resultOp = StartOpenPath(*edge_o, pt);1859#else1860StartOpenPath(*edge_o, pt);1861#endif1862}1863else1864#ifdef USINGZ1865resultOp = StartOpenPath(*edge_o, pt);1866#else1867StartOpenPath(*edge_o, pt);1868#endif18691870#ifdef USINGZ1871if (zCallback_) SetZ(*edge_o, *edge_c, resultOp->pt);1872#endif1873return;1874} // end of an open path intersection18751876//MANAGING CLOSED PATHS FROM HERE ON18771878if (IsJoined(e1)) Split(e1, pt);1879if (IsJoined(e2)) Split(e2, pt);18801881//UPDATE WINDING COUNTS...18821883int old_e1_windcnt, old_e2_windcnt;1884if (e1.local_min->polytype == e2.local_min->polytype)1885{1886if (fillrule_ == FillRule::EvenOdd)1887{1888old_e1_windcnt = e1.wind_cnt;1889e1.wind_cnt = e2.wind_cnt;1890e2.wind_cnt = old_e1_windcnt;1891}1892else1893{1894if (e1.wind_cnt + e2.wind_dx == 0)1895e1.wind_cnt = -e1.wind_cnt;1896else1897e1.wind_cnt += e2.wind_dx;1898if (e2.wind_cnt - e1.wind_dx == 0)1899e2.wind_cnt = -e2.wind_cnt;1900else1901e2.wind_cnt -= e1.wind_dx;1902}1903}1904else1905{1906if (fillrule_ != FillRule::EvenOdd)1907{1908e1.wind_cnt2 += e2.wind_dx;1909e2.wind_cnt2 -= e1.wind_dx;1910}1911else1912{1913e1.wind_cnt2 = (e1.wind_cnt2 == 0 ? 1 : 0);1914e2.wind_cnt2 = (e2.wind_cnt2 == 0 ? 1 : 0);1915}1916}19171918switch (fillrule_)1919{1920case FillRule::EvenOdd:1921case FillRule::NonZero:1922old_e1_windcnt = abs(e1.wind_cnt);1923old_e2_windcnt = abs(e2.wind_cnt);1924break;1925default:1926if (fillrule_ == fillpos)1927{1928old_e1_windcnt = e1.wind_cnt;1929old_e2_windcnt = e2.wind_cnt;1930}1931else1932{1933old_e1_windcnt = -e1.wind_cnt;1934old_e2_windcnt = -e2.wind_cnt;1935}1936break;1937}19381939const bool e1_windcnt_in_01 = old_e1_windcnt == 0 || old_e1_windcnt == 1;1940const bool e2_windcnt_in_01 = old_e2_windcnt == 0 || old_e2_windcnt == 1;19411942if ((!IsHotEdge(e1) && !e1_windcnt_in_01) ||1943(!IsHotEdge(e2) && !e2_windcnt_in_01))1944return;19451946//NOW PROCESS THE INTERSECTION ...1947#ifdef USINGZ1948OutPt* resultOp = nullptr;1949#endif1950//if both edges are 'hot' ...1951if (IsHotEdge(e1) && IsHotEdge(e2))1952{1953if ((old_e1_windcnt != 0 && old_e1_windcnt != 1) || (old_e2_windcnt != 0 && old_e2_windcnt != 1) ||1954(e1.local_min->polytype != e2.local_min->polytype && cliptype_ != ClipType::Xor))1955{1956#ifdef USINGZ1957resultOp = AddLocalMaxPoly(e1, e2, pt);1958if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt);1959#else1960AddLocalMaxPoly(e1, e2, pt);1961#endif1962}1963else if (IsFront(e1) || (e1.outrec == e2.outrec))1964{1965//this 'else if' condition isn't strictly needed but1966//it's sensible to split polygons that only touch at1967//a common vertex (not at common edges).19681969#ifdef USINGZ1970resultOp = AddLocalMaxPoly(e1, e2, pt);1971OutPt* op2 = AddLocalMinPoly(e1, e2, pt);1972if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt);1973if (zCallback_) SetZ(e1, e2, op2->pt);1974#else1975AddLocalMaxPoly(e1, e2, pt);1976AddLocalMinPoly(e1, e2, pt);1977#endif1978}1979else1980{1981#ifdef USINGZ1982resultOp = AddOutPt(e1, pt);1983OutPt* op2 = AddOutPt(e2, pt);1984if (zCallback_)1985{1986SetZ(e1, e2, resultOp->pt);1987SetZ(e1, e2, op2->pt);1988}1989#else1990AddOutPt(e1, pt);1991AddOutPt(e2, pt);1992#endif1993SwapOutrecs(e1, e2);1994}1995}1996else if (IsHotEdge(e1))1997{1998#ifdef USINGZ1999resultOp = AddOutPt(e1, pt);2000if (zCallback_) SetZ(e1, e2, resultOp->pt);2001#else2002AddOutPt(e1, pt);2003#endif2004SwapOutrecs(e1, e2);2005}2006else if (IsHotEdge(e2))2007{2008#ifdef USINGZ2009resultOp = AddOutPt(e2, pt);2010if (zCallback_) SetZ(e1, e2, resultOp->pt);2011#else2012AddOutPt(e2, pt);2013#endif2014SwapOutrecs(e1, e2);2015}2016else2017{2018int64_t e1Wc2, e2Wc2;2019switch (fillrule_)2020{2021case FillRule::EvenOdd:2022case FillRule::NonZero:2023e1Wc2 = abs(e1.wind_cnt2);2024e2Wc2 = abs(e2.wind_cnt2);2025break;2026default:2027if (fillrule_ == fillpos)2028{2029e1Wc2 = e1.wind_cnt2;2030e2Wc2 = e2.wind_cnt2;2031}2032else2033{2034e1Wc2 = -e1.wind_cnt2;2035e2Wc2 = -e2.wind_cnt2;2036}2037break;2038}20392040if (!IsSamePolyType(e1, e2))2041{2042#ifdef USINGZ2043resultOp = AddLocalMinPoly(e1, e2, pt, false);2044if (zCallback_) SetZ(e1, e2, resultOp->pt);2045#else2046AddLocalMinPoly(e1, e2, pt, false);2047#endif2048}2049else if (old_e1_windcnt == 1 && old_e2_windcnt == 1)2050{2051#ifdef USINGZ2052resultOp = nullptr;2053#endif2054switch (cliptype_)2055{2056case ClipType::Union:2057if (e1Wc2 <= 0 && e2Wc2 <= 0)2058#ifdef USINGZ2059resultOp = AddLocalMinPoly(e1, e2, pt, false);2060#else2061AddLocalMinPoly(e1, e2, pt, false);2062#endif2063break;2064case ClipType::Difference:2065if (((GetPolyType(e1) == PathType::Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||2066((GetPolyType(e1) == PathType::Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))2067{2068#ifdef USINGZ2069resultOp = AddLocalMinPoly(e1, e2, pt, false);2070#else2071AddLocalMinPoly(e1, e2, pt, false);2072#endif2073}2074break;2075case ClipType::Xor:2076#ifdef USINGZ2077resultOp = AddLocalMinPoly(e1, e2, pt, false);2078#else2079AddLocalMinPoly(e1, e2, pt, false);2080#endif2081break;2082default:2083if (e1Wc2 > 0 && e2Wc2 > 0)2084#ifdef USINGZ2085resultOp = AddLocalMinPoly(e1, e2, pt, false);2086#else2087AddLocalMinPoly(e1, e2, pt, false);2088#endif2089break;2090}2091#ifdef USINGZ2092if (resultOp && zCallback_) SetZ(e1, e2, resultOp->pt);2093#endif2094}2095}2096}20972098inline void ClipperBase::DeleteFromAEL(Active& e)2099{2100Active* prev = e.prev_in_ael;2101Active* next = e.next_in_ael;2102if (!prev && !next && (&e != actives_)) return; // already deleted2103if (prev)2104prev->next_in_ael = next;2105else2106actives_ = next;2107if (next) next->prev_in_ael = prev;2108delete& e;2109}211021112112inline void ClipperBase::AdjustCurrXAndCopyToSEL(const int64_t top_y)2113{2114Active* e = actives_;2115sel_ = e;2116while (e)2117{2118e->prev_in_sel = e->prev_in_ael;2119e->next_in_sel = e->next_in_ael;2120e->jump = e->next_in_sel;2121// it is safe to ignore 'joined' edges here because2122// if necessary they will be split in IntersectEdges()2123e->curr_x = TopX(*e, top_y);2124e = e->next_in_ael;2125}2126}21272128bool ClipperBase::ExecuteInternal(ClipType ct, FillRule fillrule, bool use_polytrees)2129{2130cliptype_ = ct;2131fillrule_ = fillrule;2132using_polytree_ = use_polytrees;2133Reset();2134int64_t y;2135if (ct == ClipType::NoClip || !PopScanline(y)) return true;21362137while (succeeded_)2138{2139InsertLocalMinimaIntoAEL(y);2140Active* e;2141while (PopHorz(e)) DoHorizontal(*e);2142if (horz_seg_list_.size() > 0)2143{2144ConvertHorzSegsToJoins();2145horz_seg_list_.clear();2146}2147bot_y_ = y; // bot_y_ == bottom of scanbeam2148if (!PopScanline(y)) break; // y new top of scanbeam2149DoIntersections(y);2150DoTopOfScanbeam(y);2151while (PopHorz(e)) DoHorizontal(*e);2152}2153if (succeeded_) ProcessHorzJoins();2154return succeeded_;2155}21562157inline void FixOutRecPts(OutRec* outrec)2158{2159OutPt* op = outrec->pts;2160do {2161op->outrec = outrec;2162op = op->next;2163} while (op != outrec->pts);2164}21652166inline bool SetHorzSegHeadingForward(HorzSegment& hs, OutPt* opP, OutPt* opN)2167{2168if (opP->pt.x == opN->pt.x) return false;2169if (opP->pt.x < opN->pt.x)2170{2171hs.left_op = opP;2172hs.right_op = opN;2173hs.left_to_right = true;2174}2175else2176{2177hs.left_op = opN;2178hs.right_op = opP;2179hs.left_to_right = false;2180}2181return true;2182}21832184inline bool UpdateHorzSegment(HorzSegment& hs)2185{2186OutPt* op = hs.left_op;2187OutRec* outrec = GetRealOutRec(op->outrec);2188bool outrecHasEdges = outrec->front_edge;2189int64_t curr_y = op->pt.y;2190OutPt* opP = op, * opN = op;2191if (outrecHasEdges)2192{2193OutPt* opA = outrec->pts, * opZ = opA->next;2194while (opP != opZ && opP->prev->pt.y == curr_y)2195opP = opP->prev;2196while (opN != opA && opN->next->pt.y == curr_y)2197opN = opN->next;2198}2199else2200{2201while (opP->prev != opN && opP->prev->pt.y == curr_y)2202opP = opP->prev;2203while (opN->next != opP && opN->next->pt.y == curr_y)2204opN = opN->next;2205}2206bool result =2207SetHorzSegHeadingForward(hs, opP, opN) &&2208!hs.left_op->horz;22092210if (result)2211hs.left_op->horz = &hs;2212else2213hs.right_op = nullptr; // (for sorting)2214return result;2215}22162217void ClipperBase::ConvertHorzSegsToJoins()2218{2219auto j = std::count_if(horz_seg_list_.begin(),2220horz_seg_list_.end(),2221[](HorzSegment& hs) { return UpdateHorzSegment(hs); });2222if (j < 2) return;22232224std::stable_sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter());22252226HorzSegmentList::iterator hs1 = horz_seg_list_.begin(), hs2;2227HorzSegmentList::iterator hs_end = hs1 +j;2228HorzSegmentList::iterator hs_end1 = hs_end - 1;22292230for (; hs1 != hs_end1; ++hs1)2231{2232for (hs2 = hs1 + 1; hs2 != hs_end; ++hs2)2233{2234if ((hs2->left_op->pt.x >= hs1->right_op->pt.x) ||2235(hs2->left_to_right == hs1->left_to_right) ||2236(hs2->right_op->pt.x <= hs1->left_op->pt.x)) continue;2237int64_t curr_y = hs1->left_op->pt.y;2238if (hs1->left_to_right)2239{2240while (hs1->left_op->next->pt.y == curr_y &&2241hs1->left_op->next->pt.x <= hs2->left_op->pt.x)2242hs1->left_op = hs1->left_op->next;2243while (hs2->left_op->prev->pt.y == curr_y &&2244hs2->left_op->prev->pt.x <= hs1->left_op->pt.x)2245hs2->left_op = hs2->left_op->prev;2246HorzJoin join = HorzJoin(2247DuplicateOp(hs1->left_op, true),2248DuplicateOp(hs2->left_op, false));2249horz_join_list_.emplace_back(join);2250}2251else2252{2253while (hs1->left_op->prev->pt.y == curr_y &&2254hs1->left_op->prev->pt.x <= hs2->left_op->pt.x)2255hs1->left_op = hs1->left_op->prev;2256while (hs2->left_op->next->pt.y == curr_y &&2257hs2->left_op->next->pt.x <= hs1->left_op->pt.x)2258hs2->left_op = hs2->left_op->next;2259HorzJoin join = HorzJoin(2260DuplicateOp(hs2->left_op, true),2261DuplicateOp(hs1->left_op, false));2262horz_join_list_.emplace_back(join);2263}2264}2265}2266}22672268void MoveSplits(OutRec* fromOr, OutRec* toOr)2269{2270if (!toOr->splits) toOr->splits = new OutRecList();2271OutRecList::iterator orIter = fromOr->splits->begin();2272for (; orIter != fromOr->splits->end(); ++orIter)2273toOr->splits->emplace_back(*orIter);2274fromOr->splits->clear();2275}22762277void ClipperBase::ProcessHorzJoins()2278{2279for (const HorzJoin& j : horz_join_list_)2280{2281OutRec* or1 = GetRealOutRec(j.op1->outrec);2282OutRec* or2 = GetRealOutRec(j.op2->outrec);22832284OutPt* op1b = j.op1->next;2285OutPt* op2b = j.op2->prev;2286j.op1->next = j.op2;2287j.op2->prev = j.op1;2288op1b->prev = op2b;2289op2b->next = op1b;22902291if (or1 == or2) // 'join' is really a split2292{2293or2 = NewOutRec();2294or2->pts = op1b;2295FixOutRecPts(or2);22962297//if or1->pts has moved to or2 then update or1->pts!!2298if (or1->pts->outrec == or2)2299{2300or1->pts = j.op1;2301or1->pts->outrec = or1;2302}23032304if (using_polytree_) //#498, #520, #584, D#576, #6182305{2306if (Path2ContainsPath1(or1->pts, or2->pts))2307{2308//swap or1's & or2's pts2309OutPt* tmp = or1->pts;2310or1->pts = or2->pts;2311or2->pts = tmp;2312FixOutRecPts(or1);2313FixOutRecPts(or2);2314//or2 is now inside or12315or2->owner = or1;2316}2317else if (Path2ContainsPath1(or2->pts, or1->pts))2318{2319or2->owner = or1;2320}2321else2322or2->owner = or1->owner;23232324if (!or1->splits) or1->splits = new OutRecList();2325or1->splits->emplace_back(or2);2326}2327else2328or2->owner = or1;2329}2330else // joining, not splitting2331{2332or2->pts = nullptr;2333if (using_polytree_)2334{2335SetOwner(or2, or1);2336if (or2->splits)2337MoveSplits(or2, or1); //#6182338}2339else2340or2->owner = or1;2341}2342}2343}23442345void ClipperBase::DoIntersections(const int64_t top_y)2346{2347if (BuildIntersectList(top_y))2348{2349ProcessIntersectList();2350intersect_nodes_.clear();2351}2352}23532354void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y)2355{2356Point64 ip;2357if (!GetSegmentIntersectPt(e1.bot, e1.top, e2.bot, e2.top, ip))2358ip = Point64(e1.curr_x, top_y); //parallel edges23592360//rounding errors can occasionally place the calculated intersection2361//point either below or above the scanbeam, so check and correct ...2362if (ip.y > bot_y_ || ip.y < top_y)2363{2364double abs_dx1 = std::fabs(e1.dx);2365double abs_dx2 = std::fabs(e2.dx);2366if (abs_dx1 > 100 && abs_dx2 > 100)2367{2368if (abs_dx1 > abs_dx2)2369ip = GetClosestPointOnSegment(ip, e1.bot, e1.top);2370else2371ip = GetClosestPointOnSegment(ip, e2.bot, e2.top);2372}2373else if (abs_dx1 > 100)2374ip = GetClosestPointOnSegment(ip, e1.bot, e1.top);2375else if (abs_dx2 > 100)2376ip = GetClosestPointOnSegment(ip, e2.bot, e2.top);2377else2378{2379if (ip.y < top_y) ip.y = top_y;2380else ip.y = bot_y_;2381if (abs_dx1 < abs_dx2) ip.x = TopX(e1, ip.y);2382else ip.x = TopX(e2, ip.y);2383}2384}2385intersect_nodes_.emplace_back(&e1, &e2, ip);2386}23872388bool ClipperBase::BuildIntersectList(const int64_t top_y)2389{2390if (!actives_ || !actives_->next_in_ael) return false;23912392//Calculate edge positions at the top of the current scanbeam, and from this2393//we will determine the intersections required to reach these new positions.2394AdjustCurrXAndCopyToSEL(top_y);2395//Find all edge intersections in the current scanbeam using a stable merge2396//sort that ensures only adjacent edges are intersecting. Intersect info is2397//stored in FIntersectList ready to be processed in ProcessIntersectList.2398//Re merge sorts see https://stackoverflow.com/a/46319131/35953823992400Active* left = sel_, * right, * l_end, * r_end, * curr_base, * tmp;24012402while (left && left->jump)2403{2404Active* prev_base = nullptr;2405while (left && left->jump)2406{2407curr_base = left;2408right = left->jump;2409l_end = right;2410r_end = right->jump;2411left->jump = r_end;2412while (left != l_end && right != r_end)2413{2414if (right->curr_x < left->curr_x)2415{2416tmp = right->prev_in_sel;2417for (; ; )2418{2419AddNewIntersectNode(*tmp, *right, top_y);2420if (tmp == left) break;2421tmp = tmp->prev_in_sel;2422}24232424tmp = right;2425right = ExtractFromSEL(tmp);2426l_end = right;2427Insert1Before2InSEL(tmp, left);2428if (left == curr_base)2429{2430curr_base = tmp;2431curr_base->jump = r_end;2432if (!prev_base) sel_ = curr_base;2433else prev_base->jump = curr_base;2434}2435}2436else left = left->next_in_sel;2437}2438prev_base = curr_base;2439left = r_end;2440}2441left = sel_;2442}2443return intersect_nodes_.size() > 0;2444}24452446void ClipperBase::ProcessIntersectList()2447{2448//We now have a list of intersections required so that edges will be2449//correctly positioned at the top of the scanbeam. However, it's important2450//that edge intersections are processed from the bottom up, but it's also2451//crucial that intersections only occur between adjacent edges.24522453//First we do a quicksort so intersections proceed in a bottom up order ...2454std::sort(intersect_nodes_.begin(), intersect_nodes_.end(), IntersectListSort);2455//Now as we process these intersections, we must sometimes adjust the order2456//to ensure that intersecting edges are always adjacent ...24572458IntersectNodeList::iterator node_iter, node_iter2;2459for (node_iter = intersect_nodes_.begin();2460node_iter != intersect_nodes_.end(); ++node_iter)2461{2462if (!EdgesAdjacentInAEL(*node_iter))2463{2464node_iter2 = node_iter + 1;2465while (!EdgesAdjacentInAEL(*node_iter2)) ++node_iter2;2466std::swap(*node_iter, *node_iter2);2467}24682469IntersectNode& node = *node_iter;2470IntersectEdges(*node.edge1, *node.edge2, node.pt);2471SwapPositionsInAEL(*node.edge1, *node.edge2);24722473node.edge1->curr_x = node.pt.x;2474node.edge2->curr_x = node.pt.x;2475CheckJoinLeft(*node.edge2, node.pt, true);2476CheckJoinRight(*node.edge1, node.pt, true);2477}2478}24792480void ClipperBase::SwapPositionsInAEL(Active& e1, Active& e2)2481{2482//preconditon: e1 must be immediately to the left of e22483Active* next = e2.next_in_ael;2484if (next) next->prev_in_ael = &e1;2485Active* prev = e1.prev_in_ael;2486if (prev) prev->next_in_ael = &e2;2487e2.prev_in_ael = prev;2488e2.next_in_ael = &e1;2489e1.prev_in_ael = &e2;2490e1.next_in_ael = next;2491if (!e2.prev_in_ael) actives_ = &e2;2492}24932494inline OutPt* GetLastOp(const Active& hot_edge)2495{2496OutRec* outrec = hot_edge.outrec;2497OutPt* result = outrec->pts;2498if (&hot_edge != outrec->front_edge)2499result = result->next;2500return result;2501}25022503void ClipperBase::AddTrialHorzJoin(OutPt* op)2504{2505if (op->outrec->is_open) return;2506horz_seg_list_.emplace_back(op);2507}25082509bool ClipperBase::ResetHorzDirection(const Active& horz,2510const Vertex* max_vertex, int64_t& horz_left, int64_t& horz_right)2511{2512if (horz.bot.x == horz.top.x)2513{2514//the horizontal edge is going nowhere ...2515horz_left = horz.curr_x;2516horz_right = horz.curr_x;2517Active* e = horz.next_in_ael;2518while (e && e->vertex_top != max_vertex) e = e->next_in_ael;2519return e != nullptr;2520}2521else if (horz.curr_x < horz.top.x)2522{2523horz_left = horz.curr_x;2524horz_right = horz.top.x;2525return true;2526}2527else2528{2529horz_left = horz.top.x;2530horz_right = horz.curr_x;2531return false; // right to left2532}2533}25342535void ClipperBase::DoHorizontal(Active& horz)2536/*******************************************************************************2537* Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or *2538* bottom of a scanbeam) are processed as if layered.The order in which HEs *2539* are processed doesn't matter. HEs intersect with the bottom vertices of *2540* other HEs[#] and with non-horizontal edges [*]. Once these intersections *2541* are completed, intermediate HEs are 'promoted' to the next edge in their *2542* bounds, and they in turn may be intersected[%] by other HEs. *2543* *2544* eg: 3 horizontals at a scanline: / | / / *2545* | / | (HE3)o ========%========== o *2546* o ======= o(HE2) / | / / *2547* o ============#=========*======*========#=========o (HE1) *2548* / | / | / *2549*******************************************************************************/2550{2551Point64 pt;2552bool horzIsOpen = IsOpen(horz);2553int64_t y = horz.bot.y;2554Vertex* vertex_max;2555if (horzIsOpen)2556vertex_max = GetCurrYMaximaVertex_Open(horz);2557else2558vertex_max = GetCurrYMaximaVertex(horz);25592560//// remove 180 deg.spikes and also simplify2561//// consecutive horizontals when PreserveCollinear = true2562//if (!horzIsOpen && vertex_max != horz.vertex_top)2563// TrimHorz(horz, PreserveCollinear);25642565int64_t horz_left, horz_right;2566bool is_left_to_right =2567ResetHorzDirection(horz, vertex_max, horz_left, horz_right);25682569if (IsHotEdge(horz))2570{2571#ifdef USINGZ2572OutPt* op = AddOutPt(horz, Point64(horz.curr_x, y, horz.bot.z));2573#else2574OutPt* op = AddOutPt(horz, Point64(horz.curr_x, y));2575#endif2576AddTrialHorzJoin(op);2577}25782579while (true) // loop through consec. horizontal edges2580{2581Active* e;2582if (is_left_to_right) e = horz.next_in_ael;2583else e = horz.prev_in_ael;25842585while (e)2586{2587if (e->vertex_top == vertex_max)2588{2589if (IsHotEdge(horz) && IsJoined(*e))2590Split(*e, e->top);25912592//if (IsHotEdge(horz) != IsHotEdge(*e))2593// DoError(undefined_error_i);25942595if (IsHotEdge(horz))2596{2597while (horz.vertex_top != vertex_max)2598{2599AddOutPt(horz, horz.top);2600UpdateEdgeIntoAEL(&horz);2601}2602if (is_left_to_right)2603AddLocalMaxPoly(horz, *e, horz.top);2604else2605AddLocalMaxPoly(*e, horz, horz.top);2606}2607DeleteFromAEL(*e);2608DeleteFromAEL(horz);2609return;2610}26112612//if horzEdge is a maxima, keep going until we reach2613//its maxima pair, otherwise check for break conditions2614if (vertex_max != horz.vertex_top || IsOpenEnd(horz))2615{2616//otherwise stop when 'ae' is beyond the end of the horizontal line2617if ((is_left_to_right && e->curr_x > horz_right) ||2618(!is_left_to_right && e->curr_x < horz_left)) break;26192620if (e->curr_x == horz.top.x && !IsHorizontal(*e))2621{2622pt = NextVertex(horz)->pt;2623if (is_left_to_right)2624{2625//with open paths we'll only break once past horz's end2626if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e))2627{2628if (TopX(*e, pt.y) > pt.x) break;2629}2630//otherwise we'll only break when horz's outslope is greater than e's2631else if (TopX(*e, pt.y) >= pt.x) break;2632}2633else2634{2635if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e))2636{2637if (TopX(*e, pt.y) < pt.x) break;2638}2639else if (TopX(*e, pt.y) <= pt.x) break;2640}2641}2642}26432644pt = Point64(e->curr_x, horz.bot.y);2645if (is_left_to_right)2646{2647IntersectEdges(horz, *e, pt);2648SwapPositionsInAEL(horz, *e);2649CheckJoinLeft(*e, pt);2650horz.curr_x = e->curr_x;2651e = horz.next_in_ael;2652}2653else2654{2655IntersectEdges(*e, horz, pt);2656SwapPositionsInAEL(*e, horz);2657CheckJoinRight(*e, pt);2658horz.curr_x = e->curr_x;2659e = horz.prev_in_ael;2660}26612662if (horz.outrec)2663{2664//nb: The outrec containing the op returned by IntersectEdges2665//above may no longer be associated with horzEdge.2666AddTrialHorzJoin(GetLastOp(horz));2667}2668}26692670//check if we've finished with (consecutive) horizontals ...2671if (horzIsOpen && IsOpenEnd(horz)) // ie open at top2672{2673if (IsHotEdge(horz))2674{2675AddOutPt(horz, horz.top);2676if (IsFront(horz))2677horz.outrec->front_edge = nullptr;2678else2679horz.outrec->back_edge = nullptr;2680horz.outrec = nullptr;2681}2682DeleteFromAEL(horz);2683return;2684}2685else if (NextVertex(horz)->pt.y != horz.top.y)2686break;26872688//still more horizontals in bound to process ...2689if (IsHotEdge(horz))2690AddOutPt(horz, horz.top);2691UpdateEdgeIntoAEL(&horz);26922693is_left_to_right =2694ResetHorzDirection(horz, vertex_max, horz_left, horz_right);2695}26962697if (IsHotEdge(horz))2698{2699OutPt* op = AddOutPt(horz, horz.top);2700AddTrialHorzJoin(op);2701}27022703UpdateEdgeIntoAEL(&horz); // end of an intermediate horiz.2704}27052706void ClipperBase::DoTopOfScanbeam(const int64_t y)2707{2708sel_ = nullptr; // sel_ is reused to flag horizontals (see PushHorz below)2709Active* e = actives_;2710while (e)2711{2712//nb: 'e' will never be horizontal here2713if (e->top.y == y)2714{2715e->curr_x = e->top.x;2716if (IsMaxima(*e))2717{2718e = DoMaxima(*e); // TOP OF BOUND (MAXIMA)2719continue;2720}2721else2722{2723//INTERMEDIATE VERTEX ...2724if (IsHotEdge(*e)) AddOutPt(*e, e->top);2725UpdateEdgeIntoAEL(e);2726if (IsHorizontal(*e))2727PushHorz(*e); // horizontals are processed later2728}2729}2730else // i.e. not the top of the edge2731e->curr_x = TopX(*e, y);27322733e = e->next_in_ael;2734}2735}273627372738Active* ClipperBase::DoMaxima(Active& e)2739{2740Active* next_e, * prev_e, * max_pair;2741prev_e = e.prev_in_ael;2742next_e = e.next_in_ael;2743if (IsOpenEnd(e))2744{2745if (IsHotEdge(e)) AddOutPt(e, e.top);2746if (!IsHorizontal(e))2747{2748if (IsHotEdge(e))2749{2750if (IsFront(e))2751e.outrec->front_edge = nullptr;2752else2753e.outrec->back_edge = nullptr;2754e.outrec = nullptr;2755}2756DeleteFromAEL(e);2757}2758return next_e;2759}27602761max_pair = GetMaximaPair(e);2762if (!max_pair) return next_e; // eMaxPair is horizontal27632764if (IsJoined(e)) Split(e, e.top);2765if (IsJoined(*max_pair)) Split(*max_pair, max_pair->top);27662767//only non-horizontal maxima here.2768//process any edges between maxima pair ...2769while (next_e != max_pair)2770{2771IntersectEdges(e, *next_e, e.top);2772SwapPositionsInAEL(e, *next_e);2773next_e = e.next_in_ael;2774}27752776if (IsOpen(e))2777{2778if (IsHotEdge(e))2779AddLocalMaxPoly(e, *max_pair, e.top);2780DeleteFromAEL(*max_pair);2781DeleteFromAEL(e);2782return (prev_e ? prev_e->next_in_ael : actives_);2783}27842785// e.next_in_ael== max_pair ...2786if (IsHotEdge(e))2787AddLocalMaxPoly(e, *max_pair, e.top);27882789DeleteFromAEL(e);2790DeleteFromAEL(*max_pair);2791return (prev_e ? prev_e->next_in_ael : actives_);2792}27932794void ClipperBase::Split(Active& e, const Point64& pt)2795{2796if (e.join_with == JoinWith::Right)2797{2798e.join_with = JoinWith::NoJoin;2799e.next_in_ael->join_with = JoinWith::NoJoin;2800AddLocalMinPoly(e, *e.next_in_ael, pt, true);2801}2802else2803{2804e.join_with = JoinWith::NoJoin;2805e.prev_in_ael->join_with = JoinWith::NoJoin;2806AddLocalMinPoly(*e.prev_in_ael, e, pt, true);2807}2808}28092810void ClipperBase::CheckJoinLeft(Active& e,2811const Point64& pt, bool check_curr_x)2812{2813Active* prev = e.prev_in_ael;2814if (!prev ||2815!IsHotEdge(e) || !IsHotEdge(*prev) ||2816IsHorizontal(e) || IsHorizontal(*prev) ||2817IsOpen(e) || IsOpen(*prev) ) return;2818if ((pt.y < e.top.y + 2 || pt.y < prev->top.y + 2) &&2819((e.bot.y > pt.y) || (prev->bot.y > pt.y))) return; // avoid trivial joins28202821if (check_curr_x)2822{2823if (PerpendicDistFromLineSqrd(pt, prev->bot, prev->top) > 0.25) return;2824}2825else if (e.curr_x != prev->curr_x) return;2826if (!IsCollinear(e.top, pt, prev->top)) return;28272828if (e.outrec->idx == prev->outrec->idx)2829AddLocalMaxPoly(*prev, e, pt);2830else if (e.outrec->idx < prev->outrec->idx)2831JoinOutrecPaths(e, *prev);2832else2833JoinOutrecPaths(*prev, e);2834prev->join_with = JoinWith::Right;2835e.join_with = JoinWith::Left;2836}28372838void ClipperBase::CheckJoinRight(Active& e,2839const Point64& pt, bool check_curr_x)2840{2841Active* next = e.next_in_ael;2842if (!next ||2843!IsHotEdge(e) || !IsHotEdge(*next) ||2844IsHorizontal(e) || IsHorizontal(*next) ||2845IsOpen(e) || IsOpen(*next)) return;2846if ((pt.y < e.top.y +2 || pt.y < next->top.y +2) &&2847((e.bot.y > pt.y) || (next->bot.y > pt.y))) return; // avoid trivial joins28482849if (check_curr_x)2850{2851if (PerpendicDistFromLineSqrd(pt, next->bot, next->top) > 0.35) return;2852}2853else if (e.curr_x != next->curr_x) return;2854if (!IsCollinear(e.top, pt, next->top)) return;28552856if (e.outrec->idx == next->outrec->idx)2857AddLocalMaxPoly(e, *next, pt);2858else if (e.outrec->idx < next->outrec->idx)2859JoinOutrecPaths(e, *next);2860else2861JoinOutrecPaths(*next, e);28622863e.join_with = JoinWith::Right;2864next->join_with = JoinWith::Left;2865}28662867inline bool GetHorzExtendedHorzSeg(OutPt*& op, OutPt*& op2)2868{2869OutRec* outrec = GetRealOutRec(op->outrec);2870op2 = op;2871if (outrec->front_edge)2872{2873while (op->prev != outrec->pts &&2874op->prev->pt.y == op->pt.y) op = op->prev;2875while (op2 != outrec->pts &&2876op2->next->pt.y == op2->pt.y) op2 = op2->next;2877return op2 != op;2878}2879else2880{2881while (op->prev != op2 && op->prev->pt.y == op->pt.y)2882op = op->prev;2883while (op2->next != op && op2->next->pt.y == op2->pt.y)2884op2 = op2->next;2885return op2 != op && op2->next != op;2886}2887}28882889bool BuildPath64(OutPt* op, bool reverse, bool isOpen, Path64& path)2890{2891if (!op || op->next == op || (!isOpen && op->next == op->prev))2892return false;28932894path.resize(0);2895Point64 lastPt;2896OutPt* op2;2897if (reverse)2898{2899lastPt = op->pt;2900op2 = op->prev;2901}2902else2903{2904op = op->next;2905lastPt = op->pt;2906op2 = op->next;2907}2908path.emplace_back(lastPt);29092910while (op2 != op)2911{2912if (op2->pt != lastPt)2913{2914lastPt = op2->pt;2915path.emplace_back(lastPt);2916}2917if (reverse)2918op2 = op2->prev;2919else2920op2 = op2->next;2921}29222923if (!isOpen && path.size() == 3 && IsVerySmallTriangle(*op2)) return false;2924else return true;2925}29262927bool ClipperBase::CheckBounds(OutRec* outrec)2928{2929if (!outrec->pts) return false;2930if (!outrec->bounds.IsEmpty()) return true;2931CleanCollinear(outrec);2932if (!outrec->pts ||2933!BuildPath64(outrec->pts, reverse_solution_, false, outrec->path)){2934return false;}2935outrec->bounds = GetBounds(outrec->path);2936return true;2937}29382939bool ClipperBase::CheckSplitOwner(OutRec* outrec, OutRecList* splits)2940{2941for (auto split : *splits)2942{2943if (!split->pts && split->splits &&2944CheckSplitOwner(outrec, split->splits)) return true; //#9422945split = GetRealOutRec(split);2946if (!split || split == outrec || split->recursive_split == outrec) continue;2947split->recursive_split = outrec; // prevent infinite loops29482949if (split->splits && CheckSplitOwner(outrec, split->splits))2950return true;29512952if (!CheckBounds(split) || !split->bounds.Contains(outrec->bounds) ||2953!Path2ContainsPath1(outrec->pts, split->pts)) continue;29542955if (!IsValidOwner(outrec, split)) // split is owned by outrec! (#957)2956split->owner = outrec->owner;29572958outrec->owner = split;2959return true;29602961}2962return false;2963}29642965void ClipperBase::RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath)2966{2967// pre-condition: outrec will have valid bounds2968// post-condition: if a valid path, outrec will have a polypath29692970if (outrec->polypath || outrec->bounds.IsEmpty()) return;29712972while (outrec->owner)2973{2974if (outrec->owner->splits && CheckSplitOwner(outrec, outrec->owner->splits)) break;2975if (outrec->owner->pts && CheckBounds(outrec->owner) &&2976outrec->owner->bounds.Contains(outrec->bounds) &&2977Path2ContainsPath1(outrec->pts, outrec->owner->pts)) break;2978outrec->owner = outrec->owner->owner;2979}29802981if (outrec->owner)2982{2983if (!outrec->owner->polypath)2984RecursiveCheckOwners(outrec->owner, polypath);2985outrec->polypath = outrec->owner->polypath->AddChild(outrec->path);2986}2987else2988outrec->polypath = polypath->AddChild(outrec->path);2989}29902991void Clipper64::BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen)2992{2993solutionClosed.resize(0);2994solutionClosed.reserve(outrec_list_.size());2995if (solutionOpen)2996{2997solutionOpen->resize(0);2998solutionOpen->reserve(outrec_list_.size());2999}30003001// nb: outrec_list_.size() may change in the following3002// while loop because polygons may be split during3003// calls to CleanCollinear which calls FixSelfIntersects3004for (size_t i = 0; i < outrec_list_.size(); ++i)3005{3006OutRec* outrec = outrec_list_[i];3007if (outrec->pts == nullptr) continue;30083009Path64 path;3010if (solutionOpen && outrec->is_open)3011{3012if (BuildPath64(outrec->pts, reverse_solution_, true, path))3013solutionOpen->emplace_back(std::move(path));3014}3015else3016{3017// nb: CleanCollinear can add to outrec_list_3018CleanCollinear(outrec);3019//closed paths should always return a Positive orientation3020if (BuildPath64(outrec->pts, reverse_solution_, false, path))3021solutionClosed.emplace_back(std::move(path));3022}3023}3024}30253026void Clipper64::BuildTree64(PolyPath64& polytree, Paths64& open_paths)3027{3028polytree.Clear();3029open_paths.resize(0);3030if (has_open_paths_)3031open_paths.reserve(outrec_list_.size());30323033// outrec_list_.size() is not static here because3034// CheckBounds below can indirectly add additional3035// OutRec (via FixOutRecPts & CleanCollinear)3036for (size_t i = 0; i < outrec_list_.size(); ++i)3037{3038OutRec* outrec = outrec_list_[i];3039if (!outrec || !outrec->pts) continue;30403041if (outrec->is_open)3042{3043Path64 path;3044if (BuildPath64(outrec->pts, reverse_solution_, true, path))3045open_paths.emplace_back(std::move(path));3046continue;3047}30483049if (CheckBounds(outrec))3050RecursiveCheckOwners(outrec, &polytree);3051}3052}30533054bool BuildPathD(OutPt* op, bool reverse, bool isOpen, PathD& path, double inv_scale)3055{3056if (!op || op->next == op || (!isOpen && op->next == op->prev))3057return false;30583059path.resize(0);3060Point64 lastPt;3061OutPt* op2;3062if (reverse)3063{3064lastPt = op->pt;3065op2 = op->prev;3066}3067else3068{3069op = op->next;3070lastPt = op->pt;3071op2 = op->next;3072}3073#ifdef USINGZ3074path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z);3075#else3076path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale);3077#endif30783079while (op2 != op)3080{3081if (op2->pt != lastPt)3082{3083lastPt = op2->pt;3084#ifdef USINGZ3085path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z);3086#else3087path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale);3088#endif30893090}3091if (reverse)3092op2 = op2->prev;3093else3094op2 = op2->next;3095}3096if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false;3097return true;3098}30993100void ClipperD::BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen)3101{3102solutionClosed.resize(0);3103solutionClosed.reserve(outrec_list_.size());3104if (solutionOpen)3105{3106solutionOpen->resize(0);3107solutionOpen->reserve(outrec_list_.size());3108}31093110// outrec_list_.size() is not static here because3111// CleanCollinear below can indirectly add additional3112// OutRec (via FixOutRecPts)3113for (std::size_t i = 0; i < outrec_list_.size(); ++i)3114{3115OutRec* outrec = outrec_list_[i];3116if (outrec->pts == nullptr) continue;31173118PathD path;3119if (solutionOpen && outrec->is_open)3120{3121if (BuildPathD(outrec->pts, reverse_solution_, true, path, invScale_))3122solutionOpen->emplace_back(std::move(path));3123}3124else3125{3126CleanCollinear(outrec);3127//closed paths should always return a Positive orientation3128if (BuildPathD(outrec->pts, reverse_solution_, false, path, invScale_))3129solutionClosed.emplace_back(std::move(path));3130}3131}3132}31333134void ClipperD::BuildTreeD(PolyPathD& polytree, PathsD& open_paths)3135{3136polytree.Clear();3137open_paths.resize(0);3138if (has_open_paths_)3139open_paths.reserve(outrec_list_.size());31403141// outrec_list_.size() is not static here because3142// BuildPathD below can indirectly add additional OutRec //#6073143for (size_t i = 0; i < outrec_list_.size(); ++i)3144{3145OutRec* outrec = outrec_list_[i];3146if (!outrec || !outrec->pts) continue;3147if (outrec->is_open)3148{3149PathD path;3150if (BuildPathD(outrec->pts, reverse_solution_, true, path, invScale_))3151open_paths.emplace_back(std::move(path));3152continue;3153}31543155if (CheckBounds(outrec))3156RecursiveCheckOwners(outrec, &polytree);3157}3158}31593160} // namespace clipper2lib316131623163