diff options
author | Alexander Jacocks | 2023-06-18 23:50:34 -0400 |
---|---|---|
committer | Alexander Jacocks | 2023-06-18 23:50:34 -0400 |
commit | 6b066b93253afd2416ef098ee43e062db0c532e6 (patch) | |
tree | b8d36d1309bfb5a904134270639abda1736d52eb | |
parent | 420a024ba32fb2bb90d6801c49b1fd196d6bd817 (diff) | |
download | aur-6b066b93253afd2416ef098ee43e062db0c532e6.tar.gz |
start fixing GCC13 issues
-rw-r--r-- | .SRCINFO | 12 | ||||
-rw-r--r-- | 0001-Boost-fix.patch | 125 | ||||
-rw-r--r-- | 0001-OpenEXR-GCC13.patch | 31 | ||||
-rw-r--r-- | 0001-TBB-GCC13.patch | 11 | ||||
-rw-r--r-- | OpenEXR.cmake | 18 | ||||
-rw-r--r-- | PKGBUILD | 23 | ||||
-rw-r--r-- | TBB.cmake | 18 | ||||
-rw-r--r-- | clipper.engine.cpp | 3594 | ||||
-rw-r--r-- | clipper.engine.h | 588 |
9 files changed, 4417 insertions, 3 deletions
@@ -29,10 +29,22 @@ pkgbase = bambustudio source = BambuStudio.cpp.patch source = TreeSupport.cpp.patch source = bambu-studio.sh + source = clipper.engine.h + source = TBB.cmake + source = 0001-TBB-GCC13.patch + source = OpenEXR.cmake + source = 0001-OpenEXR-GCC13.patch + source = clipper.engine.cpp sha512sums = 6c0d34f9ad8dbc74352b912268afc04f30d4e6d9eb21baabdd26c986625ae5e6d1e53c06f412d8315086abc29db54104ace32d17c1bd971b0b7cbd207f3dbd6e sha512sums = fd0c5df8bd82ad8fb96204921a407a4497406bc0d0b13ab11d241d28b6b924baf1d9557974506a8135f6c58ba0b183c6828a63b8b625edc1654484b3630af775 sha512sums = 419e7ffb8044531a1c78cd191a96c11f719b439afce674f7e31d1d2e0dc57ecc03cea27ab4ad5ee6522606630fd59ac1745b9a1b787db14893561a4495806117 sha512sums = 674fc00a73b2e5997e5f3dcf74299a2ab5dfac5114247f8b6b0c87bf14f289413ec668a39063ef10a557cc2c45ca08e52a7b1714a1f9f69763edf3a7faa1d01c sha512sums = e3cb1b072754ae6443fa136fffa263761b5e4e3da5dca1e91b7c4d577daaf01afa0affde04f1355fc404fcd336852db4ce8dc57938833f864346a0b17c12d6d6 + sha512sums = 183dcf3298432904b4fc0098bb356be6b87f703fd189eb66e7da8c39eacd4e7620ec153b2ab3746a777c887dffbd4440bc1cd1977f397044ed330598ce98e0e9 + sha512sums = 208a24cd44521907a261a5fa0a646eb469099bbe1b96805070e09cc1f288d7b8ff7d79dfd153e08f1ce5894030f87e5d89f33cfadfab8f2454f07d5495d360f1 + sha512sums = 929fec4b32e05cf88f335adc74dff0cb7e353054674d22f67b5dbe6e1a741de758c53ddeb746a2c2f2ffe7351eb0985f66c15d562069e6fedc7443163eee2171 + sha512sums = fcc49f8364b9446b34e0b96c72e5e4ff0288d2ebbdef8f38f8a488f97558133d0c826de1b7181e1484135e6a9e3b549b6c5fd66d37d8f3b13751b03a675d4e41 + sha512sums = b32b2b1d7320ddac22eb081a7d96f75b37cb8fc84d716433ab9e08143390c3f074cfecdaf81f74621bcd9e15def2451d673e7b1600c75fa7edf62736aaf9e315 + sha512sums = bb206ceefb7da698db8ce52531a865b2b1b2478431fc21307fa40300dda5c081b396f9667392fa677e8a7681682cd82812e6fa2c3f25f9ba5dc758b055d38900 pkgname = bambustudio diff --git a/0001-Boost-fix.patch b/0001-Boost-fix.patch new file mode 100644 index 000000000000..f1aad38d62ed --- /dev/null +++ b/0001-Boost-fix.patch @@ -0,0 +1,125 @@ +From 1d6cd7c2f8640db3cda194c1b9b82f1e4b321395 Mon Sep 17 00:00:00 2001 +From: "chunmao.guo" <chunmao.guo@bambulab.com> +Date: Thu, 5 Jan 2023 15:55:57 +0800 +Subject: [PATCH] FIX: limit_handles + +--- + boost/process/detail/posix/executor.hpp | 4 +++- + boost/process/detail/posix/pipe_out.hpp | 13 +++++++++++-- + boost/process/detail/used_handles.hpp | 3 +++ + boost/process/detail/windows/handles.hpp | 10 +++++++--- + 4 files changed, 24 insertions(+), 6 deletions(-) + +diff --git a/boost/process/detail/posix/executor.hpp b/boost/process/detail/posix/executor.hpp +index ca7713c..5521720 100644 +--- a/boost/process/detail/posix/executor.hpp ++++ b/boost/process/detail/posix/executor.hpp +@@ -325,6 +325,7 @@ public: + } + void set_error(const std::error_code &ec, const std::string &msg) {set_error(ec, msg.c_str());}; + ++ int error_sink() const { return _pipe_sink; } + }; + + template<typename Sequence> +@@ -388,6 +389,8 @@ child executor<Sequence>::invoke(boost::mpl::false_, boost::mpl::false_) + set_error(err, "fcntl(2) failed");//this might throw, so we need to be sure our pipe is safe. + return child(); + } ++ _pipe_sink = p.p[1]; ++ + _ec.clear(); + boost::fusion::for_each(seq, call_on_setup(*this)); + +@@ -411,7 +414,6 @@ child executor<Sequence>::invoke(boost::mpl::false_, boost::mpl::false_) + } + else if (pid == 0) + { +- _pipe_sink = p.p[1]; + ::close(p.p[0]); + + boost::fusion::for_each(seq, call_on_exec_setup(*this)); +diff --git a/boost/process/detail/posix/pipe_out.hpp b/boost/process/detail/posix/pipe_out.hpp +index d54cca4..a081d78 100644 +--- a/boost/process/detail/posix/pipe_out.hpp ++++ b/boost/process/detail/posix/pipe_out.hpp +@@ -18,7 +18,7 @@ + namespace boost { namespace process { namespace detail { namespace posix { + + template<int p1, int p2> +-struct pipe_out : handler_base_ext ++struct pipe_out : handler_base_ext, ::boost::process::detail::uses_handles + { + int sink; + int source; //opposite end +@@ -30,6 +30,14 @@ struct pipe_out : handler_base_ext + { + p.assign_sink(-1); + } ++ ++ std::array<int, 4> get_used_handles() ++ { ++ const auto pp1 = p1 != -1 ? p1 : p2; ++ const auto pp2 = p2 != -1 ? p2 : p1; ++ ++ return {sink, source, pp1, pp2}; ++ } + + template<typename Executor> + void on_error(Executor &, const std::error_code &) const +@@ -66,7 +74,7 @@ void pipe_out<2,-1>::on_exec_setup(Executor &e) const + if (::dup2(sink, STDERR_FILENO) == -1) + e.set_error(::boost::process::detail::get_last_error(), "dup2() failed"); + +- if (sink != STDOUT_FILENO) ++ if (sink != STDERR_FILENO) + ::close(sink); + ::close(source); + } +@@ -81,6 +89,7 @@ void pipe_out<1,2>::on_exec_setup(Executor &e) const + e.set_error(::boost::process::detail::get_last_error(), "dup2() failed"); + if ((sink != STDOUT_FILENO) && (sink != STDERR_FILENO)) + ::close(sink); ++ ::close(source); + } + + class async_pipe; +diff --git a/boost/process/detail/used_handles.hpp b/boost/process/detail/used_handles.hpp +index 4d56af3..5d71dc3 100644 +--- a/boost/process/detail/used_handles.hpp ++++ b/boost/process/detail/used_handles.hpp +@@ -61,6 +61,9 @@ struct foreach_handle_invocator + template<typename Executor, typename Function> + void foreach_used_handle(Executor &exec, Function &&func) + { ++#if defined(BOOST_POSIX_API) ++ func(exec.error_sink()); ++#endif + boost::fusion::for_each(boost::fusion::filter_if<does_use_handle<boost::mpl::_>>(exec.seq), + foreach_handle_invocator<Function>(func)); + } +diff --git a/boost/process/detail/windows/handles.hpp b/boost/process/detail/windows/handles.hpp +index 7a93ac2..f120ef7 100644 +--- a/boost/process/detail/windows/handles.hpp ++++ b/boost/process/detail/windows/handles.hpp +@@ -139,10 +139,14 @@ struct limit_handles_ : handler_base_ext + ::boost::winapi::DWORD_ flags = 0u; + if (itr != all_handles.end()) + *itr = ::boost::winapi::INVALID_HANDLE_VALUE_; +- else if ((::boost::winapi::GetHandleInformation(*itr, &flags) != 0) +- &&((flags & ::boost::winapi::HANDLE_FLAG_INHERIT_) == 0)) //it is NOT inherited anyhow, so ignore too +- *itr = ::boost::winapi::INVALID_HANDLE_VALUE_; + }); ++ for (auto& h : all_handles) { ++ ::boost::winapi::DWORD_ flags = 0u; ++ if ((h != ::boost::winapi::INVALID_HANDLE_VALUE_) ++ && (::boost::winapi::GetHandleInformation(h, &flags) != 0) ++ && ((flags & ::boost::winapi::HANDLE_FLAG_INHERIT_) == 0)) //it is NOT inherited anyhow, so ignore too ++ h = ::boost::winapi::INVALID_HANDLE_VALUE_; ++ } + + auto part_itr = std::partition(all_handles.begin(), all_handles.end(), + [](::boost::winapi::HANDLE_ handle) {return handle != ::boost::winapi::INVALID_HANDLE_VALUE_;}); +-- +2.36.1.windows.1 + diff --git a/0001-OpenEXR-GCC13.patch b/0001-OpenEXR-GCC13.patch new file mode 100644 index 000000000000..bf7d6ae82b76 --- /dev/null +++ b/0001-OpenEXR-GCC13.patch @@ -0,0 +1,31 @@ +--- a/OpenEXR/IlmImf/ImfDwaCompressor.cpp ++++ b/OpenEXR/IlmImf/ImfDwaCompressor.cpp +@@ -159,6 +159,7 @@ + #include <limits> + + #include <cstddef> ++#include <cstdint> + + + // Windows specific addition to prevent the indirect import of the redefined min/max macros +--- a/OpenEXR/IlmImf/ImfHuf.h ++++ b/OpenEXR/IlmImf/ImfHuf.h +@@ -40,6 +40,8 @@ + #include "ImfExport.h" + #include "ImfNamespace.h" + ++#include <cstdint> ++ + //----------------------------------------------------------------------------- + // + // 16-bit Huffman compression and decompression: +--- a/OpenEXR/IlmImf/ImfMisc.h ++++ b/OpenEXR/IlmImf/ImfMisc.h +@@ -51,6 +51,7 @@ + #include "ImfForward.h" + + #include <cstddef> ++#include <cstdint> + #include <vector> + + diff --git a/0001-TBB-GCC13.patch b/0001-TBB-GCC13.patch new file mode 100644 index 000000000000..047daa4497b6 --- /dev/null +++ b/0001-TBB-GCC13.patch @@ -0,0 +1,11 @@ +--- a/include/tbb/task.h ++++ b/include/tbb/task.h +@@ -219,7 +219,7 @@ + #if __TBB_TASK_PRIORITY + //! Pointer to the next offloaded lower priority task. + /** Used to maintain a list of offloaded tasks inside the scheduler. **/ +- task* next_offloaded; ++ tbb::task* next_offloaded; + }; + #endif /* __TBB_TASK_PRIORITY */ + diff --git a/OpenEXR.cmake b/OpenEXR.cmake new file mode 100644 index 000000000000..12272cbd0917 --- /dev/null +++ b/OpenEXR.cmake @@ -0,0 +1,18 @@ +bambustudio_add_cmake_project(OpenEXR + # GIT_REPOSITORY https://github.com/openexr/openexr.git + URL https://github.com/AcademySoftwareFoundation/openexr/archive/refs/tags/v2.5.5.zip + URL_HASH SHA256=0307a3d7e1fa1e77e9d84d7e9a8694583fbbbfd50bdc6884e2c96b8ef6b902de + PATCH_COMMAND ${PATCH_CMD} ${CMAKE_CURRENT_LIST_DIR}/0001-OpenEXR-GCC13.patch + DEPENDS ${ZLIB_PKG} + GIT_TAG v2.5.5 + CMAKE_ARGS + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DBUILD_TESTING=OFF + -DPYILMBASE_ENABLE:BOOL=OFF + -DOPENEXR_VIEWERS_ENABLE:BOOL=OFF + -DOPENEXR_BUILD_UTILS:BOOL=OFF +) + +if (MSVC) + add_debug_dep(dep_OpenEXR) +endif ()
\ No newline at end of file @@ -17,20 +17,37 @@ source=( 'BambuStudio.cpp.patch' 'TreeSupport.cpp.patch' 'bambu-studio.sh' + 'clipper.engine.h' + 'TBB.cmake' + '0001-TBB-GCC13.patch' + 'OpenEXR.cmake' + '0001-OpenEXR-GCC13.patch' + 'clipper.engine.cpp' ) sha512sums=('6c0d34f9ad8dbc74352b912268afc04f30d4e6d9eb21baabdd26c986625ae5e6d1e53c06f412d8315086abc29db54104ace32d17c1bd971b0b7cbd207f3dbd6e' 'fd0c5df8bd82ad8fb96204921a407a4497406bc0d0b13ab11d241d28b6b924baf1d9557974506a8135f6c58ba0b183c6828a63b8b625edc1654484b3630af775' '419e7ffb8044531a1c78cd191a96c11f719b439afce674f7e31d1d2e0dc57ecc03cea27ab4ad5ee6522606630fd59ac1745b9a1b787db14893561a4495806117' '674fc00a73b2e5997e5f3dcf74299a2ab5dfac5114247f8b6b0c87bf14f289413ec668a39063ef10a557cc2c45ca08e52a7b1714a1f9f69763edf3a7faa1d01c' - 'e3cb1b072754ae6443fa136fffa263761b5e4e3da5dca1e91b7c4d577daaf01afa0affde04f1355fc404fcd336852db4ce8dc57938833f864346a0b17c12d6d6') + 'e3cb1b072754ae6443fa136fffa263761b5e4e3da5dca1e91b7c4d577daaf01afa0affde04f1355fc404fcd336852db4ce8dc57938833f864346a0b17c12d6d6' + '183dcf3298432904b4fc0098bb356be6b87f703fd189eb66e7da8c39eacd4e7620ec153b2ab3746a777c887dffbd4440bc1cd1977f397044ed330598ce98e0e9' + '208a24cd44521907a261a5fa0a646eb469099bbe1b96805070e09cc1f288d7b8ff7d79dfd153e08f1ce5894030f87e5d89f33cfadfab8f2454f07d5495d360f1' + '929fec4b32e05cf88f335adc74dff0cb7e353054674d22f67b5dbe6e1a741de758c53ddeb746a2c2f2ffe7351eb0985f66c15d562069e6fedc7443163eee2171' + 'fcc49f8364b9446b34e0b96c72e5e4ff0288d2ebbdef8f38f8a488f97558133d0c826de1b7181e1484135e6a9e3b549b6c5fd66d37d8f3b13751b03a675d4e41' + 'b32b2b1d7320ddac22eb081a7d96f75b37cb8fc84d716433ab9e08143390c3f074cfecdaf81f74621bcd9e15def2451d673e7b1600c75fa7edf62736aaf9e315' + 'bb206ceefb7da698db8ce52531a865b2b1b2478431fc21307fa40300dda5c081b396f9667392fa677e8a7681682cd82812e6fa2c3f25f9ba5dc758b055d38900') prepare() { # link up directory ln -sf BambuStudio-${pkgver} BambuStudio # add missing 0001-Boost-fix.patch cp 0001-Boost-fix.patch BambuStudio/deps/Boost - # remove invalid UTF-8 chars -# patch -p0 < "$srcdir/TreeSupport.cpp.patch" + # deal with GCC13 issues + cp 0001-OpenEXR-GCC13.patch BambuStudio/deps/OpenEXR/0001-OpenEXR-GCC13.patch + cp OpenEXR.cmake BambuStudio/deps/OpenEXR/OpenEXR.cmake + cp 0001-TBB-GCC13.patch BambuStudio/deps/TBB/0001-TBB-GCC13.patch + cp TBB.cmake BambuStudio/deps/TBB/TBB.cmake + cp clipper.engine.h BambuStudio/src/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h + cp clipper.engine.cpp BambuStudio/src/clipper2/Clipper2Lib/src/clipper.engine.cpp } build() { diff --git a/TBB.cmake b/TBB.cmake new file mode 100644 index 000000000000..304b3ffb1b23 --- /dev/null +++ b/TBB.cmake @@ -0,0 +1,18 @@ +bambustudio_add_cmake_project( + TBB + URL "https://github.com/wjakob/tbb/archive/a0dc9bf76d0120f917b641ed095360448cabc85b.tar.gz" + URL_HASH SHA256=0545cb6033bd1873fcae3ea304def720a380a88292726943ae3b9b207f322efe + PATCH_COMMAND ${PATCH_CMD} ${CMAKE_CURRENT_LIST_DIR}/0001-TBB-GCC13.patch + CMAKE_ARGS + -DTBB_BUILD_SHARED=OFF + -DTBB_BUILD_TESTS=OFF + -DTBB_BUILD_TESTS=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DCMAKE_DEBUG_POSTFIX=_debug +) + +if (MSVC) + add_debug_dep(dep_TBB) +endif () + + diff --git a/clipper.engine.cpp b/clipper.engine.cpp new file mode 100644 index 000000000000..069b52392cac --- /dev/null +++ b/clipper.engine.cpp @@ -0,0 +1,3594 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 4 November 2022 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2022 * +* Purpose : This is the main polygon clipping module * +* License : http://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#include <stdlib.h> +#include <cmath> +#include <stdexcept> +#include <vector> +#include <numeric> +#include <algorithm> +#include "clipper2/clipper.engine.h" + +#include <cstddef> +#include <cstdint> + +namespace Clipper2Lib { + + static const double FloatingPointTolerance = 1.0e-12; + static const Rect64 invalid_rect = Rect64( + std::numeric_limits<int64_t>::max(), + std::numeric_limits<int64_t>::max(), + -std::numeric_limits<int64_t>::max(), + -std::numeric_limits<int64_t>::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' (or 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 Joiner { + int idx; + OutPt* op1; + OutPt* op2; + Joiner* next1; + Joiner* next2; + Joiner* nextH; + + explicit Joiner(OutPt* op1_, OutPt* op2_, Joiner* nexth) : + op1(op1_), op2(op2_), nextH(nexth) + { + idx = -1; + next1 = op1->joiner; + op1->joiner = this; + + if (op2) + { + next2 = op2->joiner; + op2->joiner = this; + } + else + next2 = nullptr; + } + + }; + + struct LocMinSorter { + inline bool operator()(const LocalMinima* locMin1, const LocalMinima* 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<double>::max(); + else + return std::numeric_limits<double>::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<int64_t>(std::nearbyint(ae.dx * (currentY - ae.bot.y))); + // nb: std::nearbyint (or std::round) substantially *improves* performance here + // as it greatly improves the likelihood of edge adjacency in ProcessIntersectList(). + } + + + 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<double>::max(); + } + + + inline bool IsHeadingLeftHorz(const Active& e) + { + return e.dx == std::numeric_limits<double>::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 Point64 GetEndE1ClosestToEndE2( + const Active& e1, const Active& e2) + { + double d[] = { + DistanceSqr(e1.bot, e2.bot), + DistanceSqr(e1.top, e2.top), + DistanceSqr(e1.top, e2.bot), + DistanceSqr(e1.bot, e2.top) + }; + if (d[0] == 0) return e1.bot; + int idx = 0; + for (int i = 1; i < 4; ++i) + { + if (d[i] < d[idx]) idx = i; + if (d[i] == 0) break; + } + switch (idx) + { + case 1: case 2: return e1.top; + default: return e1.bot; + } + } + + Point64 GetIntersectPoint(const Active& e1, const Active& e2) + { + double b1, b2, q = (e1.dx - e2.dx); + if (std::abs(q) < 1e-5) // 1e-5 is a rough empirical limit + return GetEndE1ClosestToEndE2(e1, e2); // ie almost parallel + + if (e1.dx == 0) + { + if (IsHorizontal(e2)) return Point64(e1.bot.x, e2.bot.y); + b2 = e2.bot.y - (e2.bot.x / e2.dx); + return Point64(e1.bot.x, + static_cast<int64_t>(std::round(e1.bot.x / e2.dx + b2))); + } + else if (e2.dx == 0) + { + if (IsHorizontal(e1)) return Point64(e2.bot.x, e1.bot.y); + b1 = e1.bot.y - (e1.bot.x / e1.dx); + return Point64(e2.bot.x, + static_cast<int64_t>(std::round(e2.bot.x / e1.dx + b1))); + } + else + { + b1 = e1.bot.x - e1.bot.y * e1.dx; + b2 = e2.bot.x - e2.bot.y * e2.dx; + + q = (b2 - b1) / q; + return (abs(e1.dx) < abs(e2.dx)) ? + Point64(static_cast<int64_t>(e1.dx * q + b1), + static_cast<int64_t>((q))) : + Point64(static_cast<int64_t>(e2.dx * q + b2), + static_cast<int64_t>((q))); + } + } + + bool GetIntersectPoint(const Point64& ln1a, const Point64& ln1b, + const Point64& ln2a, const Point64& ln2b, PointD& ip) + { + ip = PointD(0, 0); + double m1, b1, m2, b2; + if (ln1b.x == ln1a.x) + { + if (ln2b.x == ln2a.x) return false; + m2 = static_cast<double>(ln2b.y - ln2a.y) / + static_cast<double>(ln2b.x - ln2a.x); + b2 = ln2a.y - m2 * ln2a.x; + ip.x = static_cast<double>(ln1a.x); + ip.y = m2 * ln1a.x + b2; + } + else if (ln2b.x == ln2a.x) + { + m1 = static_cast<double>(ln1b.y - ln1a.y) / + static_cast<double>(ln1b.x - ln1a.x); + b1 = ln1a.y - m1 * ln1a.x; + ip.x = static_cast<double>(ln2a.x); + ip.y = m1 * ln2a.x + b1; + } + else + { + m1 = static_cast<double>(ln1b.y - ln1a.y) / + static_cast<double>(ln1b.x - ln1a.x); + b1 = ln1a.y - m1 * ln1a.x; + m2 = static_cast<double>(ln2b.y - ln2a.y) / + static_cast<double>(ln2b.x - ln2a.x); + b2 = ln2a.y - m2 * ln2a.x; + if (std::abs(m1 - m2) > FloatingPointTolerance) + { + ip.x = (b2 - b1) / (m1 - m2); + ip.y = m1 * ip.x + b1; + } + else + { + ip.x = static_cast<double>(ln1a.x + ln1b.x) / 2; + ip.y = static_cast<double>(ln1a.y + ln1b.y) / 2; + } + } + return true; + } + + + 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); + } + + 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; + } + + Active* GetHorzMaximaPair(const Active& horz, const Vertex* vert_max) + { + //we can't be sure whether the MaximaPair is on the left or right, so ... + Active* result = horz.prev_in_ael; + while (result && result->curr_x >= vert_max->pt.x) + { + if (result->vertex_top == vert_max) return result; // Found! + result = result->prev_in_ael; + } + result = horz.next_in_ael; + while (result && TopX(*result, horz.top.y) <= vert_max->pt.x) + { + if (result->vertex_top == vert_max) return result; // Found! + result = result->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* InsertOp(const Point64& pt, OutPt* insertAfter) + { + OutPt* result = new OutPt(pt, insertAfter->outrec); + result->next = insertAfter->next; + insertAfter->next->prev = result; + insertAfter->next = result; + result->prev = insertAfter; + 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) + { + if (!outrec.pts) return; + OutPt* op2 = outrec.pts->next; + while (op2 != outrec.pts) + { + OutPt* tmp = op2->next; + delete op2; + op2 = tmp; + } + delete outrec.pts; + 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<double>(op2->prev->pt.y + op2->pt.y) * + static_cast<double>(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<double>(pt3.y + pt1.y) * static_cast<double>(pt3.x - pt1.x) + + static_cast<double>(pt1.y + pt2.y) * static_cast<double>(pt1.x - pt2.x) + + static_cast<double>(pt2.y + pt3.y) * static_cast<double>(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 TestJoinWithPrev1(const Active& e) + { + //this is marginally quicker than TestJoinWithPrev2 + //but can only be used when e.PrevInAEL.currX is accurate + return IsHotEdge(e) && !IsOpen(e) && + e.prev_in_ael && e.prev_in_ael->curr_x == e.curr_x && + IsHotEdge(*e.prev_in_ael) && !IsOpen(*e.prev_in_ael) && + (CrossProduct(e.prev_in_ael->top, e.bot, e.top) == 0); + } + + inline bool TestJoinWithPrev2(const Active& e, const Point64& curr_pt) + { + return IsHotEdge(e) && !IsOpen(e) && + e.prev_in_ael && !IsOpen(*e.prev_in_ael) && + IsHotEdge(*e.prev_in_ael) && (e.prev_in_ael->top.y < e.bot.y) && + (std::llabs(TopX(*e.prev_in_ael, curr_pt.y) - curr_pt.x) < 2) && + (CrossProduct(e.prev_in_ael->top, curr_pt, e.top) == 0); + } + + inline bool TestJoinWithNext1(const Active& e) + { + //this is marginally quicker than TestJoinWithNext2 + //but can only be used when e.NextInAEL.currX is accurate + return IsHotEdge(e) && !IsOpen(e) && + e.next_in_ael && (e.next_in_ael->curr_x == e.curr_x) && + IsHotEdge(*e.next_in_ael) && !IsOpen(*e.next_in_ael) && + (CrossProduct(e.next_in_ael->top, e.bot, e.top) == 0); + } + + inline bool TestJoinWithNext2(const Active& e, const Point64& curr_pt) + { + return IsHotEdge(e) && !IsOpen(e) && + e.next_in_ael && !IsOpen(*e.next_in_ael) && + IsHotEdge(*e.next_in_ael) && (e.next_in_ael->top.y < e.bot.y) && + (std::llabs(TopX(*e.next_in_ael, curr_pt.y) - curr_pt.x) < 2) && + (CrossProduct(e.next_in_ael->top, curr_pt, e.top) == 0); + } + + //------------------------------------------------------------------------------ + // 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<int64_t>(); + intersect_nodes_.clear(); + DisposeAllOutRecs(); + } + + + 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; + } + std::vector<LocalMinima*>::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; + 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; + 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; + + Path64::size_type total_vertex_count = 0; + for (const Path64& path : paths) total_vertex_count += 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_++); + return true; + } + + + void ClipperBase::DisposeAllOutRecs() + { + for (auto outrec : outrec_list_) + { + if (outrec->pts) DisposeOutPts(*outrec); + delete outrec; + } + outrec_list_.resize(0); + } + + + void ClipperBase::DisposeVerticesAndLocalMinima() + { + for (auto lm : minima_list_) delete lm; + 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(new LocalMinima(&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; + 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_cnt = 0, + left_bound->wind_cnt2 = 0, + left_bound->wind_dx = -1, + left_bound->vertex_top = local_minima->vertex->prev; // ie descending + left_bound->top = left_bound->vertex_top->pt; + left_bound->outrec = nullptr; + 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_cnt = 0, + right_bound->wind_cnt2 = 0, + right_bound->wind_dx = 1, + right_bound->vertex_top = local_minima->vertex->next; // ie ascending + right_bound->top = right_bound->vertex_top->pt; + right_bound->outrec = nullptr; + 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) && TestJoinWithPrev1(*left_bound)) + { + OutPt* op = AddOutPt(*left_bound->prev_in_ael, left_bound->bot); + AddJoin(op, left_bound->outrec->pts); + } + } + + 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) && + TestJoinWithNext1(*right_bound)) + { + OutPt* op = AddOutPt(*right_bound->next_in_ael, right_bound->bot); + AddJoin(right_bound->outrec->pts, op); + } + + if (IsHorizontal(*right_bound)) + PushHorz(*right_bound); + else + 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 = new OutRec(); + outrec->idx = (unsigned)outrec_list_.size(); + outrec_list_.push_back(outrec); + outrec->pts = nullptr; + outrec->polypath = nullptr; + e1.outrec = outrec; + e2.outrec = outrec; + + //Setting the owner and inner/outer states (above) is an essential + //precursor to setting edge 'sides' (ie left and right sides of output + //polygons) and hence the orientation of output paths ... + + 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) + { + outrec->owner = 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 (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; + + UncoupleOutRec(e1); + if (!IsOpen(e1)) CleanCollinear(&outrec); + result = outrec.pts; + if (using_polytree_ && outrec.owner && !outrec.owner->front_edge) + outrec.owner = GetRealOutRec(outrec.owner->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; + } + + //an owner must have a lower idx otherwise + //it can't be a valid owner + if (e2.outrec->owner && e2.outrec->owner->idx < e1.outrec->idx) + { + if (!e1.outrec->owner || e2.outrec->owner->idx < e1.outrec->owner->idx) + e1.outrec->owner = e2.outrec->owner; + } + + //after joining, the e2.OutRec must contains no vertices ... + e2.outrec->front_edge = nullptr; + e2.outrec->back_edge = nullptr; + e2.outrec->pts = nullptr; + e2.outrec->owner = 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; + } + + + 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 && (pt == op_front->pt)) + new_op = op_front; + else if (!to_front && (pt == op_back->pt)) + new_op = op_back; + else + { + 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; + } + + + bool ClipperBase::ValidateClosedPathEx(OutPt*& outpt) + { + if (IsValidClosedPath(outpt)) return true; + if (outpt) SafeDisposeOutPts(outpt); + return false; + } + + + void ClipperBase::CleanCollinear(OutRec* outrec) + { + outrec = GetRealOutRec(outrec); + if (!outrec || outrec->is_open || + outrec->front_edge || !ValidateClosedPathEx(outrec->pts)) return; + + OutPt* startOp = outrec->pts, * op2 = startOp; + for (; ; ) + { + if (op2->joiner) return; + + //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 (!ValidateClosedPathEx(op2)) + { + outrec->pts = nullptr; + 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; + PointD ipD; + GetIntersectPoint(prevOp->pt, + splitOp->pt, splitOp->next->pt, nextNextOp->pt, ipD); + Point64 ip = Point64(ipD); +#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) + { + SafeDisposeOutPts(outrec->pts); + // outrec.pts == nil; :) + 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; + } + + SafeDeleteOutPtJoiners(splitOp->next); + SafeDeleteOutPtJoiners(splitOp); + + if (absArea2 >= 1 && + (absArea2 > absArea1 || (area2 > 0) == (area1 > 0))) + { + OutRec* newOutRec = new OutRec(); + newOutRec->idx = outrec_list_.size(); + outrec_list_.push_back(newOutRec); + newOutRec->owner = prevOp->outrec->owner; + newOutRec->polypath = nullptr; + splitOp->outrec = newOutRec; + splitOp->next->outrec = newOutRec; + + OutPt* newOp = new OutPt(ip, newOutRec); + newOp->prev = splitOp->next; + newOp->next = splitOp; + newOutRec->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; + } + } + + + void ClipperBase::SafeDisposeOutPts(OutPt*& op) + { + OutRec* outrec = GetRealOutRec(op->outrec); + if (outrec->front_edge) + outrec->front_edge->outrec = nullptr; + if (outrec->back_edge) + outrec->back_edge->outrec = nullptr; + + op->prev->next = nullptr; + while (op) + { + SafeDeleteOutPtJoiners(op); + OutPt* tmp = op->next; + delete op; + op = tmp; + } + outrec->pts = nullptr; + } + + + void ClipperBase::CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec) + { + double area1 = Area(op1); + double area2 = Area(op2); + bool signs_change = (area1 > 0) == (area2 < 0); + + if (area1 == 0 || (signs_change && std::abs(area1) < 2)) + { + SafeDisposeOutPts(op1); + outrec.pts = op2; + } + else if (area2 == 0 || (signs_change && std::abs(area2) < 2)) + { + SafeDisposeOutPts(op2); + outrec.pts = op1; + } + else + { + OutRec* newOr = new OutRec(); + newOr->idx = outrec_list_.size(); + outrec_list_.push_back(newOr); + newOr->polypath = nullptr; + + if (using_polytree_) + { + if (!outrec.splits) outrec.splits = new OutRecList(); + outrec.splits->push_back(newOr); + } + + if (std::abs(area1) >= std::abs(area2)) + { + outrec.pts = op1; + newOr->pts = op2; + } + else + { + outrec.pts = op2; + newOr->pts = op1; + } + + if ((area1 > 0) == (area2 > 0)) + newOr->owner = outrec.owner; + else + newOr->owner = &outrec; + + UpdateOutrecOwner(newOr); + CleanCollinear(newOr); + } + } + + + OutPt* ClipperBase::StartOpenPath(Active& e, const Point64& pt) + { + OutRec* outrec = new OutRec(); + outrec->idx = outrec_list_.size(); + outrec_list_.push_back(outrec); + outrec->owner = nullptr; + outrec->is_open = true; + outrec->pts = nullptr; + outrec->polypath = nullptr; + + 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 (IsHorizontal(*e)) return; + InsertScanline(e->top.y); + if (TestJoinWithPrev1(*e)) + { + OutPt* op1 = AddOutPt(*e->prev_in_ael, e->bot); + OutPt* op2 = AddOutPt(*e, e->bot); + AddJoin(op1, op2); + } + } + + + 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 (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 + + //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); + OutPt* op2 = AddLocalMinPoly(e1, e2, pt); +#ifdef USINGZ + if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt); + if (zCallback_) SetZ(e1, e2, op2->pt); +#endif + if (resultOp && resultOp->pt == op2->pt && + !IsHorizontal(e1) && !IsHorizontal(e2) && + (CrossProduct(e1.bot, resultOp->pt, e2.bot) == 0)) + AddJoin(resultOp, op2); + } + 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; + 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_joiners_) ConvertHorzTrialsToJoins(); + 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); + } + ProcessJoinerList(); + return succeeded_; + } + + 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 pt = GetIntersectPoint(e1, e2); + + //rounding errors can occasionally place the calculated intersection + //point either below or above the scanbeam, so check and correct ... + if (pt.y > bot_y_) + { + //e.curr.y is still the bottom of scanbeam + pt.y = bot_y_; + //use the more vertical of the 2 edges to derive pt.x ... + if (abs(e1.dx) < abs(e2.dx)) + pt.x = TopX(e1, bot_y_); + else + pt.x = TopX(e2, bot_y_); + } + else if (pt.y < top_y) + { + //top_y is at the top of the scanbeam + pt.y = top_y; + if (e1.top.y == top_y) + pt.x = e1.top.x; + else if (e2.top.y == top_y) + pt.x = e2.top.x; + else if (abs(e1.dx) < abs(e2.dx)) + pt.x = e1.curr_x; + else + pt.x = e2.curr_x; + } + + intersect_nodes_.push_back(IntersectNode(&e1, &e2, pt)); + } + + + 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 ... + + std::vector<IntersectNode>::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); + + if (TestJoinWithPrev2(*node.edge2, node.pt)) + { + OutPt* op1 = AddOutPt(*node.edge2->prev_in_ael, node.pt); + OutPt* op2 = AddOutPt(*node.edge2, node.pt); + if (op1 != op2) AddJoin(op1, op2); + } + else if (TestJoinWithNext2(*node.edge1, node.pt)) + { + OutPt* op1 = AddOutPt(*node.edge1, node.pt); + OutPt* op2 = AddOutPt(*node.edge1->next_in_ael, node.pt); + if (op1 != op2) AddJoin(op1, op2); + } + } + } + + + 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; + } + + + bool ClipperBase::ResetHorzDirection(const Active& horz, + const Active* max_pair, 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 != max_pair) 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 = nullptr; + Active* max_pair = nullptr; + + if (!horzIsOpen) + { + vertex_max = GetCurrYMaximaVertex(horz); + if (vertex_max) + { + max_pair = GetHorzMaximaPair(horz, vertex_max); + //remove 180 deg.spikes and also simplify + //consecutive horizontals when PreserveCollinear = true + if (vertex_max != horz.vertex_top) + TrimHorz(horz, PreserveCollinear); + } + } + + int64_t horz_left, horz_right; + bool is_left_to_right = + ResetHorzDirection(horz, max_pair, horz_left, horz_right); + + if (IsHotEdge(horz)) + AddOutPt(horz, Point64(horz.curr_x, y)); + + OutPt* op; + while (true) // loop through consec. horizontal edges + { + if (horzIsOpen && IsMaxima(horz) && !IsOpenEnd(horz)) + { + vertex_max = GetCurrYMaximaVertex(horz); + if (vertex_max) + max_pair = GetHorzMaximaPair(horz, vertex_max); + } + + Active* e; + if (is_left_to_right) e = horz.next_in_ael; + else e = horz.prev_in_ael; + + while (e) + { + + if (e == max_pair) + { + if (IsHotEdge(horz)) + { + while (horz.vertex_top != e->vertex_top) + { + AddOutPt(horz, horz.top); + UpdateEdgeIntoAEL(&horz); + } + op = AddLocalMaxPoly(horz, *e, horz.top); + if (op && !IsOpen(horz) && op->pt == horz.top) + AddTrialHorzJoin(op); + } + 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) + { + op = IntersectEdges(horz, *e, pt); + SwapPositionsInAEL(horz, *e); + // todo: check if op->pt == pt test is still needed + // expect op != pt only after AddLocalMaxPoly when horz.outrec == nullptr + if (IsHotEdge(horz) && op && !IsOpen(horz) && op->pt == pt) + AddTrialHorzJoin(op); + + if (!IsHorizontal(*e) && TestJoinWithPrev1(*e)) + { + op = AddOutPt(*e->prev_in_ael, pt); + OutPt* op2 = AddOutPt(*e, pt); + AddJoin(op, op2); + } + + horz.curr_x = e->curr_x; + e = horz.next_in_ael; + } + else + { + op = IntersectEdges(*e, horz, pt); + SwapPositionsInAEL(*e, horz); + + if (IsHotEdge(horz) && op && + !IsOpen(horz) && op->pt == pt) + AddTrialHorzJoin(op); + + if (!IsHorizontal(*e) && TestJoinWithNext1(*e)) + { + op = AddOutPt(*e, pt); + OutPt* op2 = AddOutPt(*e->next_in_ael, pt); + AddJoin(op, op2); + } + + horz.curr_x = e->curr_x; + e = horz.prev_in_ael; + } + } + + //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, max_pair, horz_left, horz_right); + } + + if (IsHotEdge(horz)) + { + op = AddOutPt(horz, horz.top); + if (!IsOpen(horz)) + AddTrialHorzJoin(op); + } + else + op = nullptr; + + if ((horzIsOpen && !IsOpenEnd(horz)) || + (!horzIsOpen && vertex_max != horz.vertex_top)) + { + UpdateEdgeIntoAEL(&horz); // this is the end of an intermediate horiz. + if (IsOpen(horz)) return; + + if (is_left_to_right && TestJoinWithNext1(horz)) + { + OutPt* op2 = AddOutPt(*horz.next_in_ael, horz.bot); + AddJoin(op, op2); + } + else if (!is_left_to_right && TestJoinWithPrev1(horz)) + { + OutPt* op2 = AddOutPt(*horz.prev_in_ael, horz.bot); + AddJoin(op2, op); + } + } + else if (IsHotEdge(horz)) + AddLocalMaxPoly(horz, *max_pair, horz.top); + else + { + DeleteFromAEL(*max_pair); + DeleteFromAEL(horz); + } + } + + + 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; + } + else + { + max_pair = GetMaximaPair(e); + if (!max_pair) return next_e; // eMaxPair is horizontal + } + + //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_); + } + + //here E.next_in_ael == ENext == EMaxPair ... + 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::SafeDeleteOutPtJoiners(OutPt* op) + { + Joiner* joiner = op->joiner; + if (!joiner) return; + + while (joiner) + { + if (joiner->idx < 0) + DeleteTrialHorzJoin(op); + else if (horz_joiners_) + { + if (OutPtInTrialHorzList(joiner->op1)) + DeleteTrialHorzJoin(joiner->op1); + if (OutPtInTrialHorzList(joiner->op2)) + DeleteTrialHorzJoin(joiner->op2); + DeleteJoin(joiner); + } + else + DeleteJoin(joiner); + joiner = op->joiner; + } + } + + + Joiner* ClipperBase::GetHorzTrialParent(const OutPt* op) + { + Joiner* joiner = op->joiner; + while (joiner) + { + if (joiner->op1 == op) + { + if (joiner->next1 && joiner->next1->idx < 0) return joiner; + else joiner = joiner->next1; + } + else + { + if (joiner->next2 && joiner->next2->idx < 0) return joiner; + else joiner = joiner->next1; + } + } + return joiner; + } + + + bool ClipperBase::OutPtInTrialHorzList(OutPt* op) + { + return op->joiner && ((op->joiner->idx < 0) || GetHorzTrialParent(op)); + } + + + void ClipperBase::AddTrialHorzJoin(OutPt* op) + { + //make sure 'op' isn't added more than once + if (!op->outrec->is_open && !OutPtInTrialHorzList(op)) + horz_joiners_ = new Joiner(op, nullptr, horz_joiners_); + } + + + Joiner* FindTrialJoinParent(Joiner*& joiner, const OutPt* op) + { + Joiner* parent = joiner; + while (parent) + { + if (op == parent->op1) + { + if (parent->next1 && parent->next1->idx < 0) + { + joiner = parent->next1; + return parent; + } + parent = parent->next1; + } + else + { + if (parent->next2 && parent->next2->idx < 0) + { + joiner = parent->next2; + return parent; + } + parent = parent->next2; + } + } + return nullptr; + } + + + void ClipperBase::DeleteTrialHorzJoin(OutPt* op) + { + if (!horz_joiners_) return; + + Joiner* joiner = op->joiner; + Joiner* parentH, * parentOp = nullptr; + while (joiner) + { + if (joiner->idx < 0) + { + //first remove joiner from FHorzTrials + if (joiner == horz_joiners_) + horz_joiners_ = joiner->nextH; + else + { + parentH = horz_joiners_; + while (parentH->nextH != joiner) + parentH = parentH->nextH; + parentH->nextH = joiner->nextH; + } + + //now remove joiner from op's joiner list + if (!parentOp) + { + //joiner must be first one in list + op->joiner = joiner->next1; + delete joiner; + joiner = op->joiner; + } + else + { + //the trial joiner isn't first + if (op == parentOp->op1) + parentOp->next1 = joiner->next1; + else + parentOp->next2 = joiner->next1; + delete joiner; + joiner = parentOp; + } + } + else + { + //not a trial join so look further along the linked list + parentOp = FindTrialJoinParent(joiner, op); + if (!parentOp) break; + } + //loop in case there's more than one trial join + } + } + + + 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 bool HorzEdgesOverlap(int64_t x1a, int64_t x1b, int64_t x2a, int64_t x2b) + { + const int64_t minOverlap = 2; + if (x1a > x1b + minOverlap) + { + if (x2a > x2b + minOverlap) + return !((x1a <= x2b) || (x2a <= x1b)); + else + return !((x1a <= x2a) || (x2b <= x1b)); + } + else if (x1b > x1a + minOverlap) + { + if (x2a > x2b + minOverlap) + return !((x1b <= x2b) || (x2a <= x1a)); + else + return !((x1b <= x2a) || (x2b <= x1a)); + } + else + return false; + } + + + inline bool ValueBetween(int64_t val, int64_t end1, int64_t end2) + { + //NB accommodates axis aligned between where end1 == end2 + return ((val != end1) == (val != end2)) && + ((val > end1) == (val < end2)); + } + + + inline bool ValueEqualOrBetween(int64_t val, int64_t end1, int64_t end2) + { + return (val == end1) || (val == end2) || ((val > end1) == (val < end2)); + } + + + inline bool PointBetween(Point64 pt, Point64 corner1, Point64 corner2) + { + //NB points may not be collinear + return ValueBetween(pt.x, corner1.x, corner2.x) && + ValueBetween(pt.y, corner1.y, corner2.y); + } + + inline bool PointEqualOrBetween(Point64 pt, Point64 corner1, Point64 corner2) + { + //NB points may not be collinear + return ValueEqualOrBetween(pt.x, corner1.x, corner2.x) && + ValueEqualOrBetween(pt.y, corner1.y, corner2.y); + } + + + Joiner* FindJoinParent(const Joiner* joiner, OutPt* op) + { + Joiner* result = op->joiner; + for (; ; ) + { + if (op == result->op1) + { + if (result->next1 == joiner) return result; + else result = result->next1; + } + else + { + if (result->next2 == joiner) return result; + else result = result->next2; + } + } + } + + + void ClipperBase::ConvertHorzTrialsToJoins() + { + while (horz_joiners_) + { + Joiner* joiner = horz_joiners_; + horz_joiners_ = horz_joiners_->nextH; + OutPt* op1a = joiner->op1; + if (op1a->joiner == joiner) + { + op1a->joiner = joiner->next1; + } + else + { + Joiner* joinerParent = FindJoinParent(joiner, op1a); + if (joinerParent->op1 == op1a) + joinerParent->next1 = joiner->next1; + else + joinerParent->next2 = joiner->next1; + } + delete joiner; + + OutPt* op1b; + if (!GetHorzExtendedHorzSeg(op1a, op1b)) + { + CleanCollinear(op1a->outrec); + continue; + } + + bool joined = false; + joiner = horz_joiners_; + while (joiner) + { + OutPt* op2a = joiner->op1, * op2b; + if (GetHorzExtendedHorzSeg(op2a, op2b) && + HorzEdgesOverlap(op1a->pt.x, op1b->pt.x, op2a->pt.x, op2b->pt.x)) + { + //overlap found so promote to a 'real' join + joined = true; + if (op1a->pt == op2b->pt) + AddJoin(op1a, op2b); + else if (op1b->pt == op2a->pt) + AddJoin(op1b, op2a); + else if (op1a->pt == op2a->pt) + AddJoin(op1a, op2a); + else if (op1b->pt == op2b->pt) + AddJoin(op1b, op2b); + else if (ValueBetween(op1a->pt.x, op2a->pt.x, op2b->pt.x)) + AddJoin(op1a, InsertOp(op1a->pt, op2a)); + else if (ValueBetween(op1b->pt.x, op2a->pt.x, op2b->pt.x)) + AddJoin(op1b, InsertOp(op1b->pt, op2a)); + else if (ValueBetween(op2a->pt.x, op1a->pt.x, op1b->pt.x)) + AddJoin(op2a, InsertOp(op2a->pt, op1a)); + else if (ValueBetween(op2b->pt.x, op1a->pt.x, op1b->pt.x)) + AddJoin(op2b, InsertOp(op2b->pt, op1a)); + break; + } + joiner = joiner->nextH; + } + if (!joined) + CleanCollinear(op1a->outrec); + } + } + + + void ClipperBase::AddJoin(OutPt* op1, OutPt* op2) + { + if ((op1->outrec == op2->outrec) && ((op1 == op2) || + //unless op1.next or op1.prev crosses the start-end divide + //don't waste time trying to join adjacent vertices + ((op1->next == op2) && (op1 != op1->outrec->pts)) || + ((op2->next == op1) && (op2 != op1->outrec->pts)))) return; + + Joiner* j = new Joiner(op1, op2, nullptr); + j->idx = static_cast<int>(joiner_list_.size()); + joiner_list_.push_back(j); + } + + + void ClipperBase::DeleteJoin(Joiner* joiner) + { + //This method deletes a single join, and it doesn't check for or + //delete trial horz. joins. For that, use the following method. + OutPt* op1 = joiner->op1, * op2 = joiner->op2; + + Joiner* parent_joiner; + if (op1->joiner != joiner) + { + parent_joiner = FindJoinParent(joiner, op1); + if (parent_joiner->op1 == op1) + parent_joiner->next1 = joiner->next1; + else + parent_joiner->next2 = joiner->next1; + } + else + op1->joiner = joiner->next1; + + if (op2->joiner != joiner) + { + parent_joiner = FindJoinParent(joiner, op2); + if (parent_joiner->op1 == op2) + parent_joiner->next1 = joiner->next2; + else + parent_joiner->next2 = joiner->next2; + } + else + op2->joiner = joiner->next2; + + joiner_list_[joiner->idx] = nullptr; + delete joiner; + } + + + void ClipperBase::ProcessJoinerList() + { + for (Joiner* j : joiner_list_) + { + if (!j) continue; + if (succeeded_) + { + OutRec* outrec = ProcessJoin(j); + CleanCollinear(outrec); + } + else + delete j; + } + + joiner_list_.resize(0); + } + + + bool CheckDisposeAdjacent(OutPt*& op, const OutPt* guard, OutRec& outRec) + { + bool result = false; + while (op->prev != op) + { + if (op->pt == op->prev->pt && op != guard && + op->prev->joiner && !op->joiner) + { + if (op == outRec.pts) outRec.pts = op->prev; + op = DisposeOutPt(op); + op = op->prev; + } + else + break; + } + + while (op->next != op) + { + if (op->pt == op->next->pt && op != guard && + op->next->joiner && !op->joiner) + { + if (op == outRec.pts) outRec.pts = op->prev; + op = DisposeOutPt(op); + op = op->prev; + } + else + break; + } + return result; + } + + + inline bool IsValidPath(OutPt* op) + { + return (op && op->next != op); + } + + + bool CollinearSegsOverlap(const Point64& seg1a, const Point64& seg1b, + const Point64& seg2a, const Point64& seg2b) + { + //precondition: seg1 and seg2 are collinear + if (seg1a.x == seg1b.x) + { + if (seg2a.x != seg1a.x || seg2a.x != seg2b.x) return false; + } + else if (seg1a.x < seg1b.x) + { + if (seg2a.x < seg2b.x) + { + if (seg2a.x >= seg1b.x || seg2b.x <= seg1a.x) return false; + } + else + { + if (seg2b.x >= seg1b.x || seg2a.x <= seg1a.x) return false; + } + } + else + { + if (seg2a.x < seg2b.x) + { + if (seg2a.x >= seg1a.x || seg2b.x <= seg1b.x) return false; + } + else + { + if (seg2b.x >= seg1a.x || seg2a.x <= seg1b.x) return false; + } + } + + if (seg1a.y == seg1b.y) + { + if (seg2a.y != seg1a.y || seg2a.y != seg2b.y) return false; + } + else if (seg1a.y < seg1b.y) + { + if (seg2a.y < seg2b.y) + { + if (seg2a.y >= seg1b.y || seg2b.y <= seg1a.y) return false; + } + else + { + if (seg2b.y >= seg1b.y || seg2a.y <= seg1a.y) return false; + } + } + else + { + if (seg2a.y < seg2b.y) + { + if (seg2a.y >= seg1a.y || seg2b.y <= seg1b.y) return false; + } + else + { + if (seg2b.y >= seg1a.y || seg2a.y <= seg1b.y) return false; + } + } + return true; + } + + OutRec* ClipperBase::ProcessJoin(Joiner* joiner) + { + OutPt* op1 = joiner->op1, * op2 = joiner->op2; + OutRec* or1 = GetRealOutRec(op1->outrec); + OutRec* or2 = GetRealOutRec(op2->outrec); + DeleteJoin(joiner); + + if (or2->pts == nullptr) return or1; + else if (!IsValidClosedPath(op2)) + { + SafeDisposeOutPts(op2); + return or1; + } + else if ((or1->pts == nullptr) || !IsValidClosedPath(op1)) + { + SafeDisposeOutPts(op1); + return or2; + } + else if (or1 == or2 && + ((op1 == op2) || (op1->next == op2) || (op1->prev == op2))) return or1; + + CheckDisposeAdjacent(op1, op2, *or1); + CheckDisposeAdjacent(op2, op1, *or2); + if (op1->next == op2 || op2->next == op1) return or1; + OutRec* result = or1; + + for (; ; ) + { + if (!IsValidPath(op1) || !IsValidPath(op2) || + (or1 == or2 && (op1->prev == op2 || op1->next == op2))) return or1; + + if (op1->prev->pt == op2->next->pt || + ((CrossProduct(op1->prev->pt, op1->pt, op2->next->pt) == 0) && + CollinearSegsOverlap(op1->prev->pt, op1->pt, op2->pt, op2->next->pt))) + { + if (or1 == or2) + { + //SPLIT REQUIRED + //make sure op1.prev and op2.next match positions + //by inserting an extra vertex if needed + if (op1->prev->pt != op2->next->pt) + { + if (PointEqualOrBetween(op1->prev->pt, op2->pt, op2->next->pt)) + op2->next = InsertOp(op1->prev->pt, op2); + else + op1->prev = InsertOp(op2->next->pt, op1->prev); + } + + //current to new + //op1.p[opA] >>> op1 ... opA \ / op1 + //op2.n[opB] <<< op2 ... opB / \ op2 + OutPt* opA = op1->prev, * opB = op2->next; + opA->next = opB; + opB->prev = opA; + op1->prev = op2; + op2->next = op1; + CompleteSplit(op1, opA, *or1); + } + else + { + //JOIN, NOT SPLIT + OutPt* opA = op1->prev, * opB = op2->next; + opA->next = opB; + opB->prev = opA; + op1->prev = op2; + op2->next = op1; + + //SafeDeleteOutPtJoiners(op2); + //DisposeOutPt(op2); + + if (or1->idx < or2->idx) + { + or1->pts = op1; + or2->pts = nullptr; + if (or1->owner && (!or2->owner || + or2->owner->idx < or1->owner->idx)) + or1->owner = or2->owner; + or2->owner = or1; + } + else + { + result = or2; + or2->pts = op1; + or1->pts = nullptr; + if (or2->owner && (!or1->owner || + or1->owner->idx < or2->owner->idx)) + or2->owner = or1->owner; + or1->owner = or2; + } + } + break; + } + else if (op1->next->pt == op2->prev->pt || + ((CrossProduct(op1->next->pt, op2->pt, op2->prev->pt) == 0) && + CollinearSegsOverlap(op1->next->pt, op1->pt, op2->pt, op2->prev->pt))) + { + if (or1 == or2) + { + //SPLIT REQUIRED + //make sure op2.prev and op1.next match positions + //by inserting an extra vertex if needed + if (op2->prev->pt != op1->next->pt) + { + if (PointEqualOrBetween(op2->prev->pt, op1->pt, op1->next->pt)) + op1->next = InsertOp(op2->prev->pt, op1); + else + op2->prev = InsertOp(op1->next->pt, op2->prev); + } + + //current to new + //op2.p[opA] >>> op2 ... opA \ / op2 + //op1.n[opB] <<< op1 ... opB / \ op1 + OutPt* opA = op2->prev, * opB = op1->next; + opA->next = opB; + opB->prev = opA; + op2->prev = op1; + op1->next = op2; + CompleteSplit(op1, opA, *or1); + } + else + { + //JOIN, NOT SPLIT + OutPt* opA = op1->next, * opB = op2->prev; + opA->prev = opB; + opB->next = opA; + op1->next = op2; + op2->prev = op1; + + //SafeDeleteOutPtJoiners(op2); + //DisposeOutPt(op2); + + if (or1->idx < or2->idx) + { + or1->pts = op1; + or2->pts = nullptr; + if (or1->owner && (!or2->owner || + or2->owner->idx < or1->owner->idx)) + or1->owner = or2->owner; + or2->owner = or1; + } + else + { + result = or2; + or2->pts = op1; + or1->pts = nullptr; + if (or2->owner && (!or1->owner || + or1->owner->idx < or2->owner->idx)) + or2->owner = or1->owner; + or1->owner = or2; + } + } + break; + } + else if (PointBetween(op1->next->pt, op2->pt, op2->prev->pt) && + DistanceFromLineSqrd(op1->next->pt, op2->pt, op2->prev->pt) < 2.01) + { + InsertOp(op1->next->pt, op2->prev); + continue; + } + else if (PointBetween(op2->next->pt, op1->pt, op1->prev->pt) && + DistanceFromLineSqrd(op2->next->pt, op1->pt, op1->prev->pt) < 2.01) + { + InsertOp(op2->next->pt, op1->prev); + continue; + } + else if (PointBetween(op1->prev->pt, op2->pt, op2->next->pt) && + DistanceFromLineSqrd(op1->prev->pt, op2->pt, op2->next->pt) < 2.01) + { + InsertOp(op1->prev->pt, op2); + continue; + } + else if (PointBetween(op2->prev->pt, op1->pt, op1->next->pt) && + DistanceFromLineSqrd(op2->prev->pt, op1->pt, op1->next->pt) < 2.01) + { + InsertOp(op2->prev->pt, op1); + continue; + } + + //something odd needs tidying up + if (CheckDisposeAdjacent(op1, op2, *or1)) continue; + else if (CheckDisposeAdjacent(op2, op1, *or1)) continue; + else if (op1->prev->pt != op2->next->pt && + (DistanceSqr(op1->prev->pt, op2->next->pt) < 2.01)) + { + op1->prev->pt = op2->next->pt; + continue; + } + else if (op1->next->pt != op2->prev->pt && + (DistanceSqr(op1->next->pt, op2->prev->pt) < 2.01)) + { + op2->prev->pt = op1->next->pt; + continue; + } + else + { + //OK, there doesn't seem to be a way to join after all + //so just tidy up the polygons + or1->pts = op1; + if (or2 != or1) + { + or2->pts = op2; + CleanCollinear(or2); + } + break; + } + } + return result; + + } + + inline bool Path1InsidePath2(const OutRec* or1, const OutRec* or2) + { + PointInPolygonResult result = PointInPolygonResult::IsOn; + OutPt* op = or1->pts; + do + { + result = PointInPolygon(op->pt, or2->path); + if (result != PointInPolygonResult::IsOn) break; + op = op->next; + } while (op != or1->pts); + if (result == PointInPolygonResult::IsOn) + return Area(op) < Area(or2->pts); + else + return result == PointInPolygonResult::IsInside; + } + + 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->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::DeepCheckOwner(OutRec* outrec, OutRec* owner) + { + if (owner->bounds.IsEmpty()) owner->bounds = GetBounds(owner->path); + bool is_inside_owner_bounds = owner->bounds.Contains(outrec->bounds); + + // while looking for the correct owner, check the owner's + // splits **before** checking the owner itself because + // splits can occur internally, and checking the owner + // first would miss the inner split's true ownership + if (owner->splits) + { + for (OutRec* split : *owner->splits) + { + split = GetRealOutRec(split); + if (!split || split->idx <= owner->idx || split == outrec) continue; + + if (split->splits && DeepCheckOwner(outrec, split)) return true; + + if (!split->path.size()) + BuildPath64(split->pts, ReverseSolution, false, split->path); + if (split->bounds.IsEmpty()) split->bounds = GetBounds(split->path); + + if (split->bounds.Contains(outrec->bounds) && + Path1InsidePath2(outrec, split)) + { + outrec->owner = split; + return true; + } + } + } + + // only continue past here when not inside recursion + if (owner != outrec->owner) return false; + + for (;;) + { + if (is_inside_owner_bounds && Path1InsidePath2(outrec, outrec->owner)) + return true; + // otherwise keep trying with owner's owner + outrec->owner = outrec->owner->owner; + if (!outrec->owner) return true; // true or false + is_inside_owner_bounds = outrec->owner->bounds.Contains(outrec->bounds); + } + } + + 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()); + } + + for (OutRec* outrec : outrec_list_) + { + if (outrec->pts == nullptr) continue; + + Path64 path; + if (solutionOpen && outrec->is_open) + { + if (BuildPath64(outrec->pts, ReverseSolution, true, path)) + solutionOpen->emplace_back(std::move(path)); + } + else + { + //closed paths should always return a Positive orientation + if (BuildPath64(outrec->pts, ReverseSolution, false, path)) + solutionClosed.emplace_back(std::move(path)); + } + } + } + + void Clipper64::BuildTree64(PolyPath64& polytree, Paths64& open_paths) + { + polytree.Clear(); + open_paths.resize(0); + if (has_open_paths_) + open_paths.reserve(outrec_list_.size()); + + for (OutRec* outrec : outrec_list_) + { + 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 (!BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)) + continue; + if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path); + outrec->owner = GetRealOutRec(outrec->owner); + if (outrec->owner) DeepCheckOwner(outrec, outrec->owner); + + // swap the order when a child preceeds its owner + // (because owners must preceed children in polytrees) + if (outrec->owner && outrec->idx < outrec->owner->idx) + { + OutRec* tmp = outrec->owner; + outrec_list_[outrec->owner->idx] = outrec; + outrec_list_[outrec->idx] = tmp; + size_t tmp_idx = outrec->idx; + outrec->idx = tmp->idx; + tmp->idx = tmp_idx; + outrec = tmp; + outrec->owner = GetRealOutRec(outrec->owner); + BuildPath64(outrec->pts, ReverseSolution, false, outrec->path); + if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path); + if (outrec->owner) DeepCheckOwner(outrec, outrec->owner); + } + + PolyPath* owner_polypath; + if (outrec->owner && outrec->owner->polypath) + owner_polypath = outrec->owner->polypath; + else + owner_polypath = &polytree; + outrec->polypath = owner_polypath->AddChild(outrec->path); + } + } + + bool BuildPathD(OutPt* op, bool reverse, bool isOpen, PathD& path, double inv_scale) + { + if (op->next == op || (!isOpen && op->next == op->prev)) return false; + path.resize(0); + Point64 lastPt; + OutPt* op2; + if (reverse) + { + lastPt = op->pt; + op2 = op->prev; + } + else + { + op = op->next; + lastPt = op->pt; + op2 = op->next; + } + path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale)); + + while (op2 != op) + { + if (op2->pt != lastPt) + { + lastPt = op2->pt; + path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale)); + } + if (reverse) + op2 = op2->prev; + else + op2 = op2->next; + } + 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 + { + //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 (!BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)) + continue; + if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path); + outrec->owner = GetRealOutRec(outrec->owner); + if (outrec->owner) DeepCheckOwner(outrec, outrec->owner); + + // swap the order when a child preceeds its owner + // (because owners must preceed children in polytrees) + if (outrec->owner && outrec->idx < outrec->owner->idx) + { + OutRec* tmp = outrec->owner; + outrec_list_[outrec->owner->idx] = outrec; + outrec_list_[outrec->idx] = tmp; + size_t tmp_idx = outrec->idx; + outrec->idx = tmp->idx; + tmp->idx = tmp_idx; + outrec = tmp; + outrec->owner = GetRealOutRec(outrec->owner); + BuildPath64(outrec->pts, ReverseSolution, false, outrec->path); + if (outrec->bounds.IsEmpty()) outrec->bounds = GetBounds(outrec->path); + if (outrec->owner) DeepCheckOwner(outrec, outrec->owner); + } + + PolyPath* owner_polypath; + if (outrec->owner && outrec->owner->polypath) + owner_polypath = outrec->owner->polypath; + else + owner_polypath = &polytree; + outrec->polypath = owner_polypath->AddChild(outrec->path); + } + } + +} // namespace clipper2lib diff --git a/clipper.engine.h b/clipper.engine.h new file mode 100644 index 000000000000..67383f213052 --- /dev/null +++ b/clipper.engine.h @@ -0,0 +1,588 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 4 November 2022 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2022 * +* Purpose : This is the main polygon clipping module * +* License : http://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#ifndef CLIPPER_ENGINE_H +#define CLIPPER_ENGINE_H + +constexpr auto CLIPPER2_VERSION = "1.0.6"; + +#include <cstdlib> +#include <queue> +#include <stdexcept> +#include <vector> +#include <functional> +#include <cstddef> +#include <cstdint> +#include "clipper.core.h" + +namespace Clipper2Lib { + + struct Scanline; + struct IntersectNode; + struct Active; + struct Vertex; + struct LocalMinima; + struct OutRec; + struct Joiner; + + //Note: all clipping operations except for Difference are commutative. + enum class ClipType { None, Intersection, Union, Difference, Xor }; + + enum class PathType { Subject, Clip }; + + enum class VertexFlags : uint32_t { + None = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8 + }; + + constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b) + { + return (enum VertexFlags)(uint32_t(a) & uint32_t(b)); + } + + constexpr enum VertexFlags operator |(enum VertexFlags a, enum VertexFlags b) + { + return (enum VertexFlags)(uint32_t(a) | uint32_t(b)); + } + + struct Vertex { + Point64 pt; + Vertex* next = nullptr; + Vertex* prev = nullptr; + VertexFlags flags = VertexFlags::None; + }; + + struct OutPt { + Point64 pt; + OutPt* next = nullptr; + OutPt* prev = nullptr; + OutRec* outrec; + Joiner* joiner = nullptr; + + OutPt(const Point64& pt_, OutRec* outrec_): pt(pt_), outrec(outrec_) { + next = this; + prev = this; + } + }; + + class PolyPath; + class PolyPath64; + class PolyPathD; + using PolyTree64 = PolyPath64; + using PolyTreeD = PolyPathD; + + struct OutRec; + typedef std::vector<OutRec*> OutRecList; + + //OutRec: contains a path in the clipping solution. Edges in the AEL will + //have OutRec pointers assigned when they form part of the clipping solution. + struct OutRec { + size_t idx = 0; + OutRec* owner = nullptr; + OutRecList* splits = nullptr; + Active* front_edge = nullptr; + Active* back_edge = nullptr; + OutPt* pts = nullptr; + PolyPath* polypath = nullptr; + Rect64 bounds = {}; + Path64 path; + bool is_open = false; + ~OutRec() { if (splits) delete splits; }; + }; + + /////////////////////////////////////////////////////////////////// + //Important: UP and DOWN here are premised on Y-axis positive down + //displays, which is the orientation used in Clipper's development. + /////////////////////////////////////////////////////////////////// + + struct Active { + Point64 bot; + Point64 top; + int64_t curr_x = 0; //current (updated at every new scanline) + double dx = 0.0; + int wind_dx = 1; //1 or -1 depending on winding direction + int wind_cnt = 0; + int wind_cnt2 = 0; //winding count of the opposite polytype + OutRec* outrec = nullptr; + //AEL: 'active edge list' (Vatti's AET - active edge table) + // a linked list of all edges (from left to right) that are present + // (or 'active') within the current scanbeam (a horizontal 'beam' that + // sweeps from bottom to top over the paths in the clipping operation). + Active* prev_in_ael = nullptr; + Active* next_in_ael = nullptr; + //SEL: 'sorted edge list' (Vatti's ST - sorted table) + // linked list used when sorting edges into their new positions at the + // top of scanbeams, but also (re)used to process horizontals. + Active* prev_in_sel = nullptr; + Active* next_in_sel = nullptr; + Active* jump = nullptr; + Vertex* vertex_top = nullptr; + LocalMinima* local_min = nullptr; // the bottom of an edge 'bound' (also Vatti) + bool is_left_bound = false; + }; + + struct LocalMinima { + Vertex* vertex; + PathType polytype; + bool is_open; + LocalMinima(Vertex* v, PathType pt, bool open) : + vertex(v), polytype(pt), is_open(open){} + }; + + struct IntersectNode { + Point64 pt; + Active* edge1; + Active* edge2; + IntersectNode() : pt(Point64(0, 0)), edge1(NULL), edge2(NULL) {} + IntersectNode(Active* e1, Active* e2, Point64& pt_) : + pt(pt_), edge1(e1), edge2(e2) + { + } + }; + +#ifdef USINGZ + typedef std::function<void(const Point64& e1bot, const Point64& e1top, + const Point64& e2bot, const Point64& e2top, Point64& pt)> ZCallback64; + + typedef std::function<void(const PointD& e1bot, const PointD& e1top, + const PointD& e2bot, const PointD& e2top, PointD& pt)> ZCallbackD; +#endif + + // ClipperBase ------------------------------------------------------------- + + class ClipperBase { + private: + ClipType cliptype_ = ClipType::None; + FillRule fillrule_ = FillRule::EvenOdd; + FillRule fillpos = FillRule::Positive; + int64_t bot_y_ = 0; + bool minima_list_sorted_ = false; + bool using_polytree_ = false; + Active* actives_ = nullptr; + Active *sel_ = nullptr; + Joiner *horz_joiners_ = nullptr; + std::vector<LocalMinima*> minima_list_; //pointers in case of memory reallocs + std::vector<LocalMinima*>::iterator current_locmin_iter_; + std::vector<Vertex*> vertex_lists_; + std::priority_queue<int64_t> scanline_list_; + std::vector<IntersectNode> intersect_nodes_; + std::vector<Joiner*> joiner_list_; //pointers in case of memory reallocs + void Reset(); + void InsertScanline(int64_t y); + bool PopScanline(int64_t &y); + bool PopLocalMinima(int64_t y, LocalMinima *&local_minima); + void DisposeAllOutRecs(); + void DisposeVerticesAndLocalMinima(); + void DeleteEdges(Active*& e); + void AddLocMin(Vertex &vert, PathType polytype, bool is_open); + bool IsContributingClosed(const Active &e) const; + inline bool IsContributingOpen(const Active &e) const; + void SetWindCountForClosedPathEdge(Active &edge); + void SetWindCountForOpenPathEdge(Active &e); + void InsertLocalMinimaIntoAEL(int64_t bot_y); + void InsertLeftEdge(Active &e); + inline void PushHorz(Active &e); + inline bool PopHorz(Active *&e); + inline OutPt* StartOpenPath(Active &e, const Point64& pt); + inline void UpdateEdgeIntoAEL(Active *e); + OutPt* IntersectEdges(Active &e1, Active &e2, const Point64& pt); + inline void DeleteFromAEL(Active &e); + inline void AdjustCurrXAndCopyToSEL(const int64_t top_y); + void DoIntersections(const int64_t top_y); + void AddNewIntersectNode(Active &e1, Active &e2, const int64_t top_y); + bool BuildIntersectList(const int64_t top_y); + void ProcessIntersectList(); + void SwapPositionsInAEL(Active& edge1, Active& edge2); + OutPt* AddOutPt(const Active &e, const Point64& pt); + OutPt* AddLocalMinPoly(Active &e1, Active &e2, + const Point64& pt, bool is_new = false); + OutPt* AddLocalMaxPoly(Active &e1, Active &e2, const Point64& pt); + void DoHorizontal(Active &horz); + bool ResetHorzDirection(const Active &horz, const Active *max_pair, + int64_t &horz_left, int64_t &horz_right); + void DoTopOfScanbeam(const int64_t top_y); + Active *DoMaxima(Active &e); + void JoinOutrecPaths(Active &e1, Active &e2); + void CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec); + bool ValidateClosedPathEx(OutPt*& outrec); + void CleanCollinear(OutRec* outrec); + void FixSelfIntersects(OutRec* outrec); + void DoSplitOp(OutRec* outRec, OutPt* splitOp); + Joiner* GetHorzTrialParent(const OutPt* op); + bool OutPtInTrialHorzList(OutPt* op); + void SafeDisposeOutPts(OutPt*& op); + void SafeDeleteOutPtJoiners(OutPt* op); + void AddTrialHorzJoin(OutPt* op); + void DeleteTrialHorzJoin(OutPt* op); + void ConvertHorzTrialsToJoins(); + void AddJoin(OutPt* op1, OutPt* op2); + void DeleteJoin(Joiner* joiner); + void ProcessJoinerList(); + OutRec* ProcessJoin(Joiner* joiner); + protected: + bool has_open_paths_ = false; + bool succeeded_ = true; + std::vector<OutRec*> outrec_list_; //pointers in case list memory reallocated + bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees); + bool DeepCheckOwner(OutRec* outrec, OutRec* owner); +#ifdef USINGZ + ZCallback64 zCallback_ = nullptr; + void SetZ(const Active& e1, const Active& e2, Point64& pt); +#endif + void CleanUp(); // unlike Clear, CleanUp preserves added paths + void AddPath(const Path64& path, PathType polytype, bool is_open); + void AddPaths(const Paths64& paths, PathType polytype, bool is_open); + public: + virtual ~ClipperBase(); + bool PreserveCollinear = true; + bool ReverseSolution = false; + void Clear(); + }; + + // PolyPath / PolyTree -------------------------------------------------------- + + //PolyTree: is intended as a READ-ONLY data structure for CLOSED paths returned + //by clipping operations. While this structure is more complex than the + //alternative Paths structure, it does preserve path 'ownership' - ie those + //paths that contain (or own) other paths. This will be useful to some users. + + class PolyPath { + protected: + PolyPath* parent_; + public: + PolyPath(PolyPath* parent = nullptr): parent_(parent){} + virtual ~PolyPath() { Clear(); }; + //https://en.cppreference.com/w/cpp/language/rule_of_three + PolyPath(const PolyPath&) = delete; + PolyPath& operator=(const PolyPath&) = delete; + + unsigned Level() const + { + unsigned result = 0; + const PolyPath* p = parent_; + while (p) { ++result; p = p->parent_; } + return result; + } + + virtual PolyPath* AddChild(const Path64& path) = 0; + + virtual void Clear() {}; + virtual size_t Count() const { return 0; } + + const PolyPath* Parent() const { return parent_; } + + bool IsHole() const + { + const PolyPath* pp = parent_; + bool is_hole = pp; + while (pp) { + is_hole = !is_hole; + pp = pp->parent_; + } + return is_hole; + } + }; + + class PolyPath64 : public PolyPath { + private: + std::vector<PolyPath64*> childs_; + Path64 polygon_; + typedef typename std::vector<PolyPath64*>::const_iterator pp64_itor; + public: + PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {} + PolyPath64* operator [] (size_t index) { return static_cast<PolyPath64*>(childs_[index]); } + pp64_itor begin() const { return childs_.cbegin(); } + pp64_itor end() const { return childs_.cend(); } + + PolyPath64* AddChild(const Path64& path) override + { + PolyPath64* result = new PolyPath64(this); + childs_.push_back(result); + result->polygon_ = path; + return result; + } + + void Clear() override + { + for (const PolyPath64* child : childs_) delete child; + childs_.resize(0); + } + + size_t Count() const override + { + return childs_.size(); + } + + const Path64& Polygon() const { return polygon_; }; + + double Area() const + { + double result = Clipper2Lib::Area<int64_t>(polygon_); + for (const PolyPath64* child : childs_) + result += child->Area(); + return result; + } + + friend std::ostream& operator << (std::ostream& outstream, const PolyPath64& polypath) + { + const size_t level_indent = 4; + const size_t coords_per_line = 4; + const size_t last_on_line = coords_per_line - 1; + unsigned level = polypath.Level(); + if (level > 0) + { + std::string level_padding; + level_padding.insert(0, (level - 1) * level_indent, ' '); + std::string caption = polypath.IsHole() ? "Hole " : "Outer Polygon "; + std::string childs = polypath.Count() == 1 ? " child" : " children"; + outstream << level_padding.c_str() << caption << "with " << polypath.Count() << childs << std::endl; + outstream << level_padding; + size_t i = 0, highI = polypath.Polygon().size() - 1; + for (; i < highI; ++i) + { + outstream << polypath.Polygon()[i] << ' '; + if ((i % coords_per_line) == last_on_line) + outstream << std::endl << level_padding; + } + if (highI > 0) outstream << polypath.Polygon()[i]; + outstream << std::endl; + } + for (auto child : polypath) + outstream << *child; + return outstream; + } + + }; + + class PolyPathD : public PolyPath { + private: + std::vector<PolyPathD*> childs_; + double inv_scale_; + PathD polygon_; + typedef typename std::vector<PolyPathD*>::const_iterator ppD_itor; + public: + PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent) + { + inv_scale_ = parent ? parent->inv_scale_ : 1.0; + } + PolyPathD* operator [] (size_t index) + { + return static_cast<PolyPathD*>(childs_[index]); + } + ppD_itor begin() const { return childs_.cbegin(); } + ppD_itor end() const { return childs_.cend(); } + + void SetInvScale(double value) { inv_scale_ = value; } + double InvScale() { return inv_scale_; } + PolyPathD* AddChild(const Path64& path) override + { + PolyPathD* result = new PolyPathD(this); + childs_.push_back(result); + result->polygon_ = ScalePath<double, int64_t>(path, inv_scale_); + return result; + } + + void Clear() override + { + for (const PolyPathD* child : childs_) delete child; + childs_.resize(0); + } + + size_t Count() const override + { + return childs_.size(); + } + + const PathD& Polygon() const { return polygon_; }; + + double Area() const + { + double result = Clipper2Lib::Area<double>(polygon_); + for (const PolyPathD* child : childs_) + result += child->Area(); + return result; + } + }; + + class Clipper64 : public ClipperBase + { + private: + void BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen); + void BuildTree64(PolyPath64& polytree, Paths64& open_paths); + public: +#ifdef USINGZ + void SetZCallback(ZCallback64 cb) { zCallback_ = cb; } +#endif + + void AddSubject(const Paths64& subjects) + { + AddPaths(subjects, PathType::Subject, false); + } + void AddOpenSubject(const Paths64& open_subjects) + { + AddPaths(open_subjects, PathType::Subject, true); + } + void AddClip(const Paths64& clips) + { + AddPaths(clips, PathType::Clip, false); + } + + bool Execute(ClipType clip_type, + FillRule fill_rule, Paths64& closed_paths) + { + Paths64 dummy; + return Execute(clip_type, fill_rule, closed_paths, dummy); + } + + bool Execute(ClipType clip_type, FillRule fill_rule, + Paths64& closed_paths, Paths64& open_paths) + { + closed_paths.clear(); + open_paths.clear(); + if (ExecuteInternal(clip_type, fill_rule, false)) + BuildPaths64(closed_paths, &open_paths); + CleanUp(); + return succeeded_; + } + + bool Execute(ClipType clip_type, FillRule fill_rule, PolyTree64& polytree) + { + Paths64 dummy; + return Execute(clip_type, fill_rule, polytree, dummy); + } + + bool Execute(ClipType clip_type, + FillRule fill_rule, PolyTree64& polytree, Paths64& open_paths) + { + if (ExecuteInternal(clip_type, fill_rule, true)) + { + open_paths.clear(); + polytree.Clear(); + BuildTree64(polytree, open_paths); + } + CleanUp(); + return succeeded_; + } + }; + + class ClipperD : public ClipperBase { + private: + double scale_ = 1.0, invScale_ = 1.0; +#ifdef USINGZ + ZCallbackD zCallback_ = nullptr; +#endif + void BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen); + void BuildTreeD(PolyPathD& polytree, PathsD& open_paths); + public: + explicit ClipperD(int precision = 2) : ClipperBase() + { + CheckPrecision(precision); + // to optimize scaling / descaling precision + // set the scale to a power of double's radix (2) (#25) + scale_ = std::pow(std::numeric_limits<double>::radix, + std::ilogb(std::pow(10, precision)) + 1); + invScale_ = 1 / scale_; + } + +#ifdef USINGZ + void SetZCallback(ZCallbackD cb) { zCallback_ = cb; }; + + void ZCB(const Point64& e1bot, const Point64& e1top, + const Point64& e2bot, const Point64& e2top, Point64& pt) + { + // de-scale (x & y) + // temporarily convert integers to their initial float values + // this will slow clipping marginally but will make it much easier + // to understand the coordinates passed to the callback function + PointD tmp = PointD(pt) * invScale_; + PointD e1b = PointD(e1bot) * invScale_; + PointD e1t = PointD(e1top) * invScale_; + PointD e2b = PointD(e2bot) * invScale_; + PointD e2t = PointD(e2top) * invScale_; + zCallback_(e1b,e1t, e2b, e2t, tmp); + pt.z = tmp.z; // only update 'z' + }; + + void CheckCallback() + { + if(zCallback_) + // if the user defined float point callback has been assigned + // then assign the proxy callback function + ClipperBase::zCallback_ = + std::bind(&ClipperD::ZCB, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, + std::placeholders::_4, std::placeholders::_5); + else + ClipperBase::zCallback_ = nullptr; + } + +#endif + + void AddSubject(const PathsD& subjects) + { + AddPaths(ScalePaths<int64_t, double>(subjects, scale_), PathType::Subject, false); + } + + void AddOpenSubject(const PathsD& open_subjects) + { + AddPaths(ScalePaths<int64_t, double>(open_subjects, scale_), PathType::Subject, true); + } + + void AddClip(const PathsD& clips) + { + AddPaths(ScalePaths<int64_t, double>(clips, scale_), PathType::Clip, false); + } + + bool Execute(ClipType clip_type, FillRule fill_rule, PathsD& closed_paths) + { + PathsD dummy; + return Execute(clip_type, fill_rule, closed_paths, dummy); + } + + bool Execute(ClipType clip_type, + FillRule fill_rule, PathsD& closed_paths, PathsD& open_paths) + { +#ifdef USINGZ + CheckCallback(); +#endif + if (ExecuteInternal(clip_type, fill_rule, false)) + { + BuildPathsD(closed_paths, &open_paths); + } + CleanUp(); + return succeeded_; + } + + bool Execute(ClipType clip_type, FillRule fill_rule, PolyTreeD& polytree) + { + PathsD dummy; + return Execute(clip_type, fill_rule, polytree, dummy); + } + + bool Execute(ClipType clip_type, + FillRule fill_rule, PolyTreeD& polytree, PathsD& open_paths) + { +#ifdef USINGZ + CheckCallback(); +#endif + if (ExecuteInternal(clip_type, fill_rule, true)) + { + polytree.Clear(); + polytree.SetInvScale(invScale_); + open_paths.clear(); + BuildTreeD(polytree, open_paths); + } + CleanUp(); + return succeeded_; + } + + }; + +} // namespace + +#endif // CLIPPER_ENGINE_H |