From 4c00966ca38da2fc5de502eeab905e83fd9ac801 Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Fri, 21 Nov 2025 12:18:22 -0300 Subject: [PATCH 1/4] Wiki 16 (MiniUpdate): Updated by Stable cmake 4.2 release + VS2026 First (#11423) * Update Visual Studio and CMake instructions in build guide Reorders Visual Studio version recommendations and simplifies installation instructions. Clarifies CMake path setup guidance and removes outdated notes specific to Visual Studio 2026 and CMake 4.2. * Update CMake mac version 3.31.10 --- doc/developer-reference/How-to-build.md | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/doc/developer-reference/How-to-build.md b/doc/developer-reference/How-to-build.md index 77cadf566d..bb06224de7 100644 --- a/doc/developer-reference/How-to-build.md +++ b/doc/developer-reference/How-to-build.md @@ -33,12 +33,7 @@ How to building with Visual Studio on Windows 64-bit. ### Windows Tools Required -- [Visual Studio](https://visualstudio.microsoft.com/vs/) 2022, 2026 or Visual Studio 2019 - - 2022 - ```shell - winget install --id=Microsoft.VisualStudio.2022.Professional -e - ``` - - 2026 +- [Visual Studio](https://visualstudio.microsoft.com/vs/) 2026, 2022 or Visual Studio 2019 ```shell winget install --id=Microsoft.VisualStudio.Community -e ``` @@ -68,19 +63,11 @@ How to building with Visual Studio on Windows 64-bit. > [!IMPORTANT] > Check your CMake version. Run `cmake --version` in your terminal and verify it returns a **4.x** version. > If you see an older version (e.g. 3.29), it's likely due to another copy in your system's PATH (e.g. from Strawberry Perl). -> You can run where cmake to check the active paths and rearrange your **System Environment Variables** > PATH, ensuring the correct CMake (e.g. C:\Program Files\CMake\bin) appears before others like C:\Strawberry\c\bin. +> You can run where cmake to check the active paths and rearrange your **System Environment Variables** > PATH, ensuring the correct CMake like `C:\Program Files\CMake\bin` appears before others like `C:\Strawberry\c\bin`. ![windows_variables_path](https://github.com/OrcaSlicer/OrcaSlicer/blob/main/doc/images/develop/windows_variables_path.png?raw=true) ![windows_variables_order](https://github.com/OrcaSlicer/OrcaSlicer/blob/main/doc/images/develop/windows_variables_order.png?raw=true) -> [!IMPORTANT] -> **For Visual Studio 2026**, you must use the CMake included with Visual Studio 2026 **until CMake 4.2 is released as a stable version**. -> To do this, you must include the cmake path contained in Visual Studio above the variable of the official cmake installed on your computer. -> The path will look something like this: -> ```shell -> C:\Program Files\Microsoft Visual Studio\18\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin -> ``` - ### Windows Hardware Requirements - Minimum 16 GB RAM @@ -169,7 +156,7 @@ How to building with Xcode on MacOS 64-bit. Homebrew currently only offers the latest version of CMake (e.g. **4.X**), which is not compatible. To install the required version **3.31.X**, follow these steps: -1. Download CMake **3.31.7** from: [https://cmake.org/download/](https://cmake.org/download/) +1. Download CMake **3.31.10** from: [https://cmake.org/download/](https://cmake.org/download/) 2. Install the application (drag it to `/Applications`). 3. Add the following line to your shell configuration file (`~/.zshrc` or `~/.bash_profile`): From 7b0cdd3ec693273ef716b02ca58d7e27dcf2e644 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sat, 22 Nov 2025 15:37:28 +0800 Subject: [PATCH 2/4] Fix crash after syncing printer (#11428) Fix crash after syncing printer (OrcaSlicer/OrcaSlicer#11427) --- src/slic3r/GUI/Plater.cpp | 4 ++-- src/slic3r/GUI/Plater.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f5c225e0a0..3af97465f0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3235,8 +3235,8 @@ void Sidebar::on_full_screen(IntEvent &e) { void Sidebar::get_big_btn_sync_pos_size(wxPoint &pt, wxSize &size) { - size =btn_sync->GetSize(); - pt = btn_sync->GetScreenPosition(); + size = p->m_printer_bbl_sync->GetSize(); + pt = p->m_printer_bbl_sync->GetScreenPosition(); } void Sidebar::get_small_btn_sync_pos_size(wxPoint &pt, wxSize &size) { diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 88787f6845..8137e2bd2d 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -127,7 +127,7 @@ public: class Sidebar : public wxPanel { ConfigOptionMode m_mode; - Button * btn_sync{nullptr}; + //Button * btn_sync{nullptr}; ScalableButton * ams_btn{nullptr}; bool m_last_slice_state = false; SyncNozzleAndAmsDialog* m_sna_dialog{nullptr}; From 61931d23a42d5bc105f8a10094deabd01c8929b7 Mon Sep 17 00:00:00 2001 From: Noisyfox Date: Sat, 22 Nov 2025 15:38:12 +0800 Subject: [PATCH 3/4] Fix filament profile import if inherit from vendor generic (#11398) * Why log user id when importing profile? * Use `find_preset2` when importing profiles to properly handle renamed system profiles --- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PresetBundle.cpp | 2 +- src/slic3r/GUI/MainFrame.cpp | 4 ---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index cef7da2dfd..1dcdd83000 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -2731,7 +2731,7 @@ Preset* PresetCollection::find_preset(const std::string &name, bool first_visibl first_visible_if_not_found ? &this->first_visible() : nullptr; } -Preset* PresetCollection::find_preset2(const std::string& name, bool auto_match) +Preset* PresetCollection::find_preset2(const std::string& name, bool auto_match/* = true */) { auto preset = find_preset(name,false,true); if (preset == nullptr) { diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 062d3930ef..27f2f93e47 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1089,7 +1089,7 @@ bool PresetBundle::import_json_presets(PresetsConfigSubstitutions & s if (inherits_config) { ConfigOptionString *option_str = dynamic_cast(inherits_config); inherits_value = option_str->value; - inherit_preset = collection->find_preset(inherits_value, false, true); + inherit_preset = collection->find_preset2(inherits_value); } if (inherit_preset) { new_config = inherit_preset->config; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index c413d2f20e..6d0023a0ba 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -3454,10 +3454,6 @@ void MainFrame::load_config_file() wxGetApp().app_config->update_config_dir(get_dir_name(cfiles.back())); wxGetApp().load_current_presets(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " presets has been import,and size is" << cfiles.size(); - NetworkAgent* agent = wxGetApp().getAgent(); - if (agent) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " user is: " << agent->get_user_id(); - } } wxGetApp().preset_bundle->update_compatible(PresetSelectCompatibleType::Always); update_side_preset_ui(); From 3367648ec0af88f545e46028410a280be51ab53c Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Sat, 22 Nov 2025 04:39:12 -0300 Subject: [PATCH 4/4] Re-include BBS's Clipperutils2 (#11415) * Clipper2 from BBS * Fix * Clipper2 Update: 1.5.2 -> 2.1.5.4 * Replace Clipper2Lib::Area calls with Clipper2LibArea Updated area calculation calls to use Clipper2LibArea instead of Clipper2Lib::Area in multiple locations. This change likely reflects a refactor or renaming of the area calculation function. * Revert "Replace Clipper2Lib::Area calls with Clipper2LibArea" This reverts commit c92a9a1522e8d7b0f1f8557d4c752a8b8c8fc279. * Revert "Clipper2 Update: 1.5.2 -> 2.1.5.4" This reverts commit a745894bdc9cda4ce4d19e0705d214b78091acad. * Update project version --- src/CMakeLists.txt | 2 + src/clipper2/CMakeLists.txt | 54 + .../include/clipper2/clipper.core.h | 1080 ++++++ .../include/clipper2/clipper.engine.h | 646 ++++ .../include/clipper2/clipper.export.h | 836 +++++ .../Clipper2Lib/include/clipper2/clipper.h | 770 ++++ .../include/clipper2/clipper.minkowski.h | 120 + .../include/clipper2/clipper.offset.h | 129 + .../include/clipper2/clipper.rectclip.h | 83 + .../include/clipper2/clipper.version.h | 6 + .../include/clipper2/clipper2_z.hpp | 17 + .../Clipper2Lib/src/clipper.engine.cpp | 3151 +++++++++++++++++ .../Clipper2Lib/src/clipper.offset.cpp | 658 ++++ .../Clipper2Lib/src/clipper.rectclip.cpp | 1031 ++++++ src/clipper2/Clipper2Lib/src/clipper2_z.cpp | 8 + src/libslic3r/CMakeLists.txt | 3 + src/libslic3r/Clipper2Utils.cpp | 211 ++ src/libslic3r/Clipper2Utils.hpp | 18 + src/libslic3r/Clipper2ZUtils.hpp | 167 + 19 files changed, 8990 insertions(+) create mode 100644 src/clipper2/CMakeLists.txt create mode 100644 src/clipper2/Clipper2Lib/include/clipper2/clipper.core.h create mode 100644 src/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h create mode 100644 src/clipper2/Clipper2Lib/include/clipper2/clipper.export.h create mode 100644 src/clipper2/Clipper2Lib/include/clipper2/clipper.h create mode 100644 src/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h create mode 100644 src/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h create mode 100644 src/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h create mode 100644 src/clipper2/Clipper2Lib/include/clipper2/clipper.version.h create mode 100644 src/clipper2/Clipper2Lib/include/clipper2/clipper2_z.hpp create mode 100644 src/clipper2/Clipper2Lib/src/clipper.engine.cpp create mode 100644 src/clipper2/Clipper2Lib/src/clipper.offset.cpp create mode 100644 src/clipper2/Clipper2Lib/src/clipper.rectclip.cpp create mode 100644 src/clipper2/Clipper2Lib/src/clipper2_z.cpp create mode 100644 src/libslic3r/Clipper2Utils.cpp create mode 100644 src/libslic3r/Clipper2Utils.hpp create mode 100644 src/libslic3r/Clipper2ZUtils.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8e9a31f705..ea7731bf30 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,6 +6,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) # Include dev-utils for encoding check and other utilities add_subdirectory(dev-utils) +# Clipper2 math utils +add_subdirectory(clipper2) # add_subdirectory(avrdude) # Note: semver and hints are now included from deps_src/CMakeLists.txt diff --git a/src/clipper2/CMakeLists.txt b/src/clipper2/CMakeLists.txt new file mode 100644 index 0000000000..c604002da7 --- /dev/null +++ b/src/clipper2/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required(VERSION 3.10) +project(Clipper2 VERSION 1.5.2 LANGUAGES C CXX) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set_property(GLOBAL PROPERTY USE_FOLDERS OFF) + +option(BUILD_SHARED_LIBS "Build shared libs" OFF) + +include(GNUInstallDirs) + +set(CLIPPER2_INC + Clipper2Lib/include/clipper2/clipper.h + Clipper2Lib/include/clipper2/clipper.core.h + Clipper2Lib/include/clipper2/clipper.engine.h + Clipper2Lib/include/clipper2/clipper.export.h + Clipper2Lib/include/clipper2/clipper.minkowski.h + Clipper2Lib/include/clipper2/clipper.offset.h + Clipper2Lib/include/clipper2/clipper.rectclip.h + Clipper2Lib/include/clipper2/clipper2_z.hpp +) + +set(CLIPPER2_SRC + Clipper2Lib/src/clipper.engine.cpp + Clipper2Lib/src/clipper.offset.cpp + Clipper2Lib/src/clipper.rectclip.cpp + Clipper2Lib/src/clipper2_z.cpp +) + +# 2d version of Clipper2 +add_library(Clipper2 ${CLIPPER2_INC} ${CLIPPER2_SRC}) + +target_include_directories(Clipper2 + PUBLIC Clipper2Lib/include +) + +if (WIN32) + target_compile_options(Clipper2 PRIVATE /W4 /WX) +else() + target_compile_options(Clipper2 PRIVATE -Wall -Wextra -Wpedantic -Werror) + target_link_libraries(Clipper2 PUBLIC -lm) + if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 14.1) + target_compile_options(Clipper2 PRIVATE -Wno-error=template-id-cdtor) + endif() +endif() + +set_target_properties(Clipper2 PROPERTIES FOLDER Libraries + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + PUBLIC_HEADER "${CLIPPER2_INC}" +) + diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.core.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.core.h new file mode 100644 index 0000000000..6d00f8fa07 --- /dev/null +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.core.h @@ -0,0 +1,1080 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 12 May 2024 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2024 * +* Purpose : Core Clipper Library structures and functions * +* License : https://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#ifndef CLIPPER_CORE_H +#define CLIPPER_CORE_H + +#include "clipper2/clipper.version.h" +#include +#include +#include +#include +#include +#include +#include + +#ifdef USINGZ +namespace Clipper2Lib_Z { +#else +namespace Clipper2Lib { +#endif + +#if (defined(__cpp_exceptions) && __cpp_exceptions) || (defined(__EXCEPTIONS) && __EXCEPTIONS) + + class Clipper2Exception : public std::exception { + public: + explicit Clipper2Exception(const char* description) : + m_descr(description) {} + virtual const char* what() const noexcept override { return m_descr.c_str(); } + private: + std::string m_descr; + }; + + static const char* precision_error = + "Precision exceeds the permitted range"; + static const char* range_error = + "Values exceed permitted range"; + static const char* scale_error = + "Invalid scale (either 0 or too large)"; + static const char* non_pair_error = + "There must be 2 values for each coordinate"; + static const char* undefined_error = + "There is an undefined error in Clipper2"; +#endif + + // error codes (2^n) + const int precision_error_i = 1; // non-fatal + const int scale_error_i = 2; // non-fatal + const int non_pair_error_i = 4; // non-fatal + const int undefined_error_i = 32; // fatal + const int range_error_i = 64; + +#ifndef PI + static const double PI = 3.141592653589793238; +#endif + +#ifdef CLIPPER2_MAX_DECIMAL_PRECISION + const int CLIPPER2_MAX_DEC_PRECISION = CLIPPER2_MAX_DECIMAL_PRECISION; +#else + const int CLIPPER2_MAX_DEC_PRECISION = 8; // see Discussions #564 +#endif + + static const int64_t MAX_COORD = INT64_MAX >> 2; + static const int64_t MIN_COORD = -MAX_COORD; + static const int64_t INVALID = INT64_MAX; + const double max_coord = static_cast(MAX_COORD); + const double min_coord = static_cast(MIN_COORD); + + static const double MAX_DBL = (std::numeric_limits::max)(); + + static void DoError([[maybe_unused]] int error_code) + { +#if (defined(__cpp_exceptions) && __cpp_exceptions) || (defined(__EXCEPTIONS) && __EXCEPTIONS) + switch (error_code) + { + case precision_error_i: + throw Clipper2Exception(precision_error); + case scale_error_i: + throw Clipper2Exception(scale_error); + case non_pair_error_i: + throw Clipper2Exception(non_pair_error); + case undefined_error_i: + throw Clipper2Exception(undefined_error); + case range_error_i: + throw Clipper2Exception(range_error); + // Should never happen, but adding this to stop a compiler warning + default: + throw Clipper2Exception("Unknown error"); + } +#else + ++error_code; // only to stop compiler warning +#endif + } + + // can we call std::round on T? (default false) (#824) + template + struct is_round_invocable : std::false_type {}; + + template + struct is_round_invocable()))>> : std::true_type {}; + + + //By far the most widely used filling rules for polygons are EvenOdd + //and NonZero, sometimes called Alternate and Winding respectively. + //https://en.wikipedia.org/wiki/Nonzero-rule + enum class FillRule { EvenOdd, NonZero, Positive, Negative }; + +#ifdef USINGZ + using z_type = int64_t; +#endif + + // Point ------------------------------------------------------------------------ + + template + struct Point { + T x; + T y; +#ifdef USINGZ + z_type z; + + template + inline void Init(const T2 x_ = 0, const T2 y_ = 0, const z_type z_ = 0) + { + if constexpr (std::is_integral_v && + is_round_invocable::value && !std::is_integral_v) + { + x = static_cast(std::round(x_)); + y = static_cast(std::round(y_)); + z = z_; + } + else + { + x = static_cast(x_); + y = static_cast(y_); + z = z_; + } + } + + explicit Point() : x(0), y(0), z(0) {}; + + template + Point(const T2 x_, const T2 y_, const z_type z_ = 0) + { + Init(x_, y_); + z = z_; + } + + template + explicit Point(const Point& p) + { + Init(p.x, p.y, p.z); + } + + template + explicit Point(const Point& p, z_type z_) + { + Init(p.x, p.y, z_); + } + + Point operator * (const double scale) const + { + return Point(x * scale, y * scale, z); + } + + void SetZ(const z_type z_value) { z = z_value; } + + friend std::ostream& operator<<(std::ostream& os, const Point& point) + { + os << point.x << "," << point.y << "," << point.z; + return os; + } + +#else + + template + inline void Init(const T2 x_ = 0, const T2 y_ = 0) + { + if constexpr (std::is_integral_v && + is_round_invocable::value && !std::is_integral_v) + { + x = static_cast(std::round(x_)); + y = static_cast(std::round(y_)); + } + else + { + x = static_cast(x_); + y = static_cast(y_); + } + } + + explicit Point() : x(0), y(0) {}; + + template + Point(const T2 x_, const T2 y_) { Init(x_, y_); } + + template + explicit Point(const Point& p) { Init(p.x, p.y); } + + Point operator * (const double scale) const + { + return Point(x * scale, y * scale); + } + + friend std::ostream& operator<<(std::ostream& os, const Point& point) + { + os << point.x << "," << point.y; + return os; + } +#endif + + friend bool operator==(const Point& a, const Point& b) + { + return a.x == b.x && a.y == b.y; + } + + friend bool operator!=(const Point& a, const Point& b) + { + return !(a == b); + } + + inline Point operator-() const + { + return Point(-x, -y); + } + + inline Point operator+(const Point& b) const + { + return Point(x + b.x, y + b.y); + } + + inline Point operator-(const Point& b) const + { + return Point(x - b.x, y - b.y); + } + + inline void Negate() { x = -x; y = -y; } + + }; + + //nb: using 'using' here (instead of typedef) as they can be used in templates + using Point64 = Point; + using PointD = Point; + + template + using Path = std::vector>; + template + using Paths = std::vector>; + + using Path64 = Path; + using PathD = Path; + using Paths64 = std::vector< Path64>; + using PathsD = std::vector< PathD>; + + static const Point64 InvalidPoint64 = Point64( + (std::numeric_limits::max)(), + (std::numeric_limits::max)()); + static const PointD InvalidPointD = PointD( + (std::numeric_limits::max)(), + (std::numeric_limits::max)()); + + template + static inline Point MidPoint(const Point& p1, const Point& p2) + { + Point result; + result.x = (p1.x + p2.x) / 2; + result.y = (p1.y + p2.y) / 2; + return result; + } + + // Rect ------------------------------------------------------------------------ + + template + struct Rect; + + using Rect64 = Rect; + using RectD = Rect; + + template + struct Rect { + T left; + T top; + T right; + T bottom; + + Rect(T l, T t, T r, T b) : + left(l), + top(t), + right(r), + bottom(b) {} + + Rect(bool is_valid = true) + { + if (is_valid) + { + left = right = top = bottom = 0; + } + else + { + left = top = (std::numeric_limits::max)(); + right = bottom = std::numeric_limits::lowest(); + } + } + + static Rect InvalidRect() + { + return { + (std::numeric_limits::max)(), + (std::numeric_limits::max)(), + std::numeric_limits::lowest(), + std::numeric_limits::lowest() }; + } + + bool IsValid() const { return left != (std::numeric_limits::max)(); } + + T Width() const { return right - left; } + T Height() const { return bottom - top; } + void Width(T width) { right = left + width; } + void Height(T height) { bottom = top + height; } + + Point MidPoint() const + { + return Point((left + right) / 2, (top + bottom) / 2); + } + + Path AsPath() const + { + Path result; + result.reserve(4); + result.emplace_back(left, top); + result.emplace_back(right, top); + result.emplace_back(right, bottom); + result.emplace_back(left, bottom); + return result; + } + + bool Contains(const Point& pt) const + { + return pt.x > left && pt.x < right&& pt.y > top && pt.y < bottom; + } + + bool Contains(const Rect& rec) const + { + return rec.left >= left && rec.right <= right && + rec.top >= top && rec.bottom <= bottom; + } + + void Scale(double scale) { + left *= scale; + top *= scale; + right *= scale; + bottom *= scale; + } + + bool IsEmpty() const { return bottom <= top || right <= left; }; + + bool Intersects(const Rect& rec) const + { + return ((std::max)(left, rec.left) <= (std::min)(right, rec.right)) && + ((std::max)(top, rec.top) <= (std::min)(bottom, rec.bottom)); + }; + + bool operator==(const Rect& other) const { + return left == other.left && right == other.right && + top == other.top && bottom == other.bottom; + } + + Rect& operator+=(const Rect& other) + { + left = (std::min)(left, other.left); + top = (std::min)(top, other.top); + right = (std::max)(right, other.right); + bottom = (std::max)(bottom, other.bottom); + return *this; + } + + Rect operator+(const Rect& other) const + { + Rect result = *this; + result += other; + return result; + } + + friend std::ostream& operator<<(std::ostream& os, const Rect& rect) { + os << "(" << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom << ") "; + return os; + } + }; + + template + inline Rect ScaleRect(const Rect& rect, double scale) + { + Rect result; + + if constexpr (std::is_integral_v && + is_round_invocable::value && !std::is_integral_v) + { + result.left = static_cast(std::round(rect.left * scale)); + result.top = static_cast(std::round(rect.top * scale)); + result.right = static_cast(std::round(rect.right * scale)); + result.bottom = static_cast(std::round(rect.bottom * scale)); + } + else + { + result.left = static_cast(rect.left * scale); + result.top = static_cast(rect.top * scale); + result.right = static_cast(rect.right * scale); + result.bottom = static_cast(rect.bottom * scale); + } + return result; + } + + static const Rect64 InvalidRect64 = Rect64::InvalidRect(); + static const RectD InvalidRectD = RectD::InvalidRect(); + + template + Rect GetBounds(const Path& path) + { + T xmin = (std::numeric_limits::max)(); + T ymin = (std::numeric_limits::max)(); + T xmax = std::numeric_limits::lowest(); + T ymax = std::numeric_limits::lowest(); + for (const auto& p : path) + { + if (p.x < xmin) xmin = p.x; + if (p.x > xmax) xmax = p.x; + if (p.y < ymin) ymin = p.y; + if (p.y > ymax) ymax = p.y; + } + return Rect(xmin, ymin, xmax, ymax); + } + + template + Rect GetBounds(const Paths& paths) + { + T xmin = (std::numeric_limits::max)(); + T ymin = (std::numeric_limits::max)(); + T xmax = std::numeric_limits::lowest(); + T ymax = std::numeric_limits::lowest(); + for (const Path& path : paths) + for (const Point& p : path) + { + if (p.x < xmin) xmin = p.x; + if (p.x > xmax) xmax = p.x; + if (p.y < ymin) ymin = p.y; + if (p.y > ymax) ymax = p.y; + } + return Rect(xmin, ymin, xmax, ymax); + } + + template + Rect GetBounds(const Path& path) + { + T xmin = (std::numeric_limits::max)(); + T ymin = (std::numeric_limits::max)(); + T xmax = std::numeric_limits::lowest(); + T ymax = std::numeric_limits::lowest(); + for (const auto& p : path) + { + if (p.x < xmin) xmin = static_cast(p.x); + if (p.x > xmax) xmax = static_cast(p.x); + if (p.y < ymin) ymin = static_cast(p.y); + if (p.y > ymax) ymax = static_cast(p.y); + } + return Rect(xmin, ymin, xmax, ymax); + } + + template + Rect GetBounds(const Paths& paths) + { + T xmin = (std::numeric_limits::max)(); + T ymin = (std::numeric_limits::max)(); + T xmax = std::numeric_limits::lowest(); + T ymax = std::numeric_limits::lowest(); + for (const Path& path : paths) + for (const Point& p : path) + { + if (p.x < xmin) xmin = static_cast(p.x); + if (p.x > xmax) xmax = static_cast(p.x); + if (p.y < ymin) ymin = static_cast(p.y); + if (p.y > ymax) ymax = static_cast(p.y); + } + return Rect(xmin, ymin, xmax, ymax); + } + + template + std::ostream& operator << (std::ostream& outstream, const Path& path) + { + if (!path.empty()) + { + auto pt = path.cbegin(), last = path.cend() - 1; + while (pt != last) + outstream << *pt++ << ", "; + outstream << *last << std::endl; + } + return outstream; + } + + template + std::ostream& operator << (std::ostream& outstream, const Paths& paths) + { + for (auto p : paths) + outstream << p; + return outstream; + } + + + template + inline Path ScalePath(const Path& path, + double scale_x, double scale_y, int& error_code) + { + Path result; + if (scale_x == 0 || scale_y == 0) + { + error_code |= scale_error_i; + DoError(scale_error_i); + // if no exception, treat as non-fatal error + if (scale_x == 0) scale_x = 1.0; + if (scale_y == 0) scale_y = 1.0; + } + + result.reserve(path.size()); +#ifdef USINGZ + std::transform(path.begin(), path.end(), back_inserter(result), + [scale_x, scale_y](const auto& pt) + { return Point(pt.x * scale_x, pt.y * scale_y, pt.z); }); +#else + std::transform(path.begin(), path.end(), back_inserter(result), + [scale_x, scale_y](const auto& pt) + { return Point(pt.x * scale_x, pt.y * scale_y); }); +#endif + return result; + } + + template + inline Path ScalePath(const Path& path, + double scale, int& error_code) + { + return ScalePath(path, scale, scale, error_code); + } + + template + inline Paths ScalePaths(const Paths& paths, + double scale_x, double scale_y, int& error_code) + { + Paths result; + + if constexpr (std::is_integral_v) + { + RectD r = GetBounds(paths); + if ((r.left * scale_x) < min_coord || + (r.right * scale_x) > max_coord || + (r.top * scale_y) < min_coord || + (r.bottom * scale_y) > max_coord) + { + error_code |= range_error_i; + DoError(range_error_i); + return result; // empty path + } + } + + result.reserve(paths.size()); + std::transform(paths.begin(), paths.end(), back_inserter(result), + [=, &error_code](const auto& path) + { return ScalePath(path, scale_x, scale_y, error_code); }); + return result; + } + + template + inline Paths ScalePaths(const Paths& paths, + double scale, int& error_code) + { + return ScalePaths(paths, scale, scale, error_code); + } + + template + inline Path TransformPath(const Path& path) + { + Path result; + result.reserve(path.size()); + std::transform(path.cbegin(), path.cend(), std::back_inserter(result), + [](const Point& pt) {return Point(pt); }); + return result; + } + + template + inline Paths TransformPaths(const Paths& paths) + { + Paths result; + std::transform(paths.cbegin(), paths.cend(), std::back_inserter(result), + [](const Path& path) {return TransformPath(path); }); + return result; + } + + template + inline double Sqr(T val) + { + return static_cast(val) * static_cast(val); + } + + template + inline bool NearEqual(const Point& p1, + const Point& p2, double max_dist_sqrd) + { + return Sqr(p1.x - p2.x) + Sqr(p1.y - p2.y) < max_dist_sqrd; + } + + template + inline Path StripNearEqual(const Path& path, + double max_dist_sqrd, bool is_closed_path) + { + if (path.size() == 0) return Path(); + Path result; + result.reserve(path.size()); + typename Path::const_iterator path_iter = path.cbegin(); + Point first_pt = *path_iter++, last_pt = first_pt; + result.emplace_back(first_pt); + for (; path_iter != path.cend(); ++path_iter) + { + if (!NearEqual(*path_iter, last_pt, max_dist_sqrd)) + { + last_pt = *path_iter; + result.emplace_back(last_pt); + } + } + if (!is_closed_path) return result; + while (result.size() > 1 && + NearEqual(result.back(), first_pt, max_dist_sqrd)) result.pop_back(); + return result; + } + + template + inline Paths StripNearEqual(const Paths& paths, + double max_dist_sqrd, bool is_closed_path) + { + Paths result; + result.reserve(paths.size()); + for (typename Paths::const_iterator paths_citer = paths.cbegin(); + paths_citer != paths.cend(); ++paths_citer) + { + result.emplace_back(std::move(StripNearEqual(*paths_citer, max_dist_sqrd, is_closed_path))); + } + return result; + } + + template + inline void StripDuplicates( Path& path, bool is_closed_path) + { + //https://stackoverflow.com/questions/1041620/whats-the-most-efficient-way-to-erase-duplicates-and-sort-a-vector#:~:text=Let%27s%20compare%20three%20approaches%3A + path.erase(std::unique(path.begin(), path.end()), path.end()); + if (is_closed_path) + while (path.size() > 1 && path.back() == path.front()) path.pop_back(); + } + + template + inline void StripDuplicates( Paths& paths, bool is_closed_path) + { + for (typename Paths::iterator paths_citer = paths.begin(); + paths_citer != paths.end(); ++paths_citer) + { + StripDuplicates(*paths_citer, is_closed_path); + } + } + + // Miscellaneous ------------------------------------------------------------ + + inline void CheckPrecisionRange(int& precision, int& error_code) + { + if (precision >= -CLIPPER2_MAX_DEC_PRECISION && + precision <= CLIPPER2_MAX_DEC_PRECISION) return; + error_code |= precision_error_i; // non-fatal error + DoError(precision_error_i); // does nothing when exceptions are disabled + precision = precision > 0 ? CLIPPER2_MAX_DEC_PRECISION : -CLIPPER2_MAX_DEC_PRECISION; + } + + inline void CheckPrecisionRange(int& precision) + { + int error_code = 0; + CheckPrecisionRange(precision, error_code); + } + + inline int TriSign(int64_t x) // returns 0, 1 or -1 + { + return (x > 0) - (x < 0); + } + + struct MultiplyUInt64Result + { + const uint64_t result = 0; + const uint64_t carry = 0; + + bool operator==(const MultiplyUInt64Result& other) const + { + return result == other.result && carry == other.carry; + }; + }; + + inline MultiplyUInt64Result Multiply(uint64_t a, uint64_t b) // #834, #835 + { + const auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; }; + const auto hi = [](uint64_t x) { return x >> 32; }; + + const uint64_t x1 = lo(a) * lo(b); + const uint64_t x2 = hi(a) * lo(b) + hi(x1); + const uint64_t x3 = lo(a) * hi(b) + lo(x2); + const uint64_t result = lo(x3) << 32 | lo(x1); + const uint64_t carry = hi(a) * hi(b) + hi(x2) + hi(x3); + + return { result, carry }; + } + + // returns true if (and only if) a * b == c * d + inline bool ProductsAreEqual(int64_t a, int64_t b, int64_t c, int64_t d) + { +#if (defined(__clang__) || defined(__GNUC__)) && UINTPTR_MAX >= UINT64_MAX + const auto ab = static_cast<__int128_t>(a) * static_cast<__int128_t>(b); + const auto cd = static_cast<__int128_t>(c) * static_cast<__int128_t>(d); + return ab == cd; +#else + // nb: unsigned values needed for calculating overflow carry + const auto abs_a = static_cast(std::abs(a)); + const auto abs_b = static_cast(std::abs(b)); + const auto abs_c = static_cast(std::abs(c)); + const auto abs_d = static_cast(std::abs(d)); + + const auto abs_ab = Multiply(abs_a, abs_b); + const auto abs_cd = Multiply(abs_c, abs_d); + + // nb: it's important to differentiate 0 values here from other values + const auto sign_ab = TriSign(a) * TriSign(b); + const auto sign_cd = TriSign(c) * TriSign(d); + + return abs_ab == abs_cd && sign_ab == sign_cd; +#endif + } + + template + inline bool IsCollinear(const Point& pt1, + const Point& sharedPt, const Point& pt2) // #777 + { + const auto a = sharedPt.x - pt1.x; + const auto b = pt2.y - sharedPt.y; + const auto c = sharedPt.y - pt1.y; + const auto d = pt2.x - sharedPt.x; + // When checking for collinearity with very large coordinate values + // then ProductsAreEqual is more accurate than using CrossProduct. + return ProductsAreEqual(a, b, c, d); + } + + + template + inline double CrossProduct(const Point& pt1, const Point& pt2, const Point& pt3) { + return (static_cast(pt2.x - pt1.x) * static_cast(pt3.y - + pt2.y) - static_cast(pt2.y - pt1.y) * static_cast(pt3.x - pt2.x)); + } + + template + inline double CrossProduct(const Point& vec1, const Point& vec2) + { + return static_cast(vec1.y * vec2.x) - static_cast(vec2.y * vec1.x); + } + + template + inline double DotProduct(const Point& pt1, const Point& pt2, const Point& pt3) { + return (static_cast(pt2.x - pt1.x) * static_cast(pt3.x - pt2.x) + + static_cast(pt2.y - pt1.y) * static_cast(pt3.y - pt2.y)); + } + + template + inline double DotProduct(const Point& vec1, const Point& vec2) + { + return static_cast(vec1.x * vec2.x) + static_cast(vec1.y * vec2.y); + } + + template + inline double DistanceSqr(const Point pt1, const Point pt2) + { + return Sqr(pt1.x - pt2.x) + Sqr(pt1.y - pt2.y); + } + + template + inline double PerpendicDistFromLineSqrd(const Point& pt, + const Point& line1, const Point& line2) + { + //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) + //see https://en.wikipedia.org/wiki/Perpendicular_distance + double a = static_cast(pt.x - line1.x); + double b = static_cast(pt.y - line1.y); + double c = static_cast(line2.x - line1.x); + double d = static_cast(line2.y - line1.y); + if (c == 0 && d == 0) return 0; + return Sqr(a * d - c * b) / (c * c + d * d); + } + + template + inline double Area(const Path& path) + { + size_t cnt = path.size(); + if (cnt < 3) return 0.0; + double a = 0.0; + typename Path::const_iterator it1, it2 = path.cend() - 1, stop = it2; + if (!(cnt & 1)) ++stop; + for (it1 = path.cbegin(); it1 != stop;) + { + a += static_cast(it2->y + it1->y) * (it2->x - it1->x); + it2 = it1 + 1; + a += static_cast(it1->y + it2->y) * (it1->x - it2->x); + it1 += 2; + } + if (cnt & 1) + a += static_cast(it2->y + it1->y) * (it2->x - it1->x); + return (a * 0.5); + } + + template + inline double Area(const Paths& paths) + { + double a = 0.0; + for (typename Paths::const_iterator paths_iter = paths.cbegin(); + paths_iter != paths.cend(); ++paths_iter) + { + a += Area(*paths_iter); + } + return a; + } + + template + inline bool IsPositive(const Path& poly) + { + // A curve has positive orientation [and area] if a region 'R' + // is on the left when traveling around the outside of 'R'. + //https://mathworld.wolfram.com/CurveOrientation.html + //nb: This statement is premised on using Cartesian coordinates + return Area(poly) >= 0; + } + +#if CLIPPER2_HI_PRECISION + // caution: this will compromise performance + // https://github.com/AngusJohnson/Clipper2/issues/317#issuecomment-1314023253 + // See also CPP/BenchMark/GetIntersectPtBenchmark.cpp + #define CC_MIN(x,y) ((x)>(y)?(y):(x)) + #define CC_MAX(x,y) ((x)<(y)?(y):(x)) + template + inline bool GetSegmentIntersectPt(const Point& ln1a, const Point& ln1b, + const Point& ln2a, const Point& ln2b, Point& ip) + { + double ln1dy = static_cast(ln1b.y - ln1a.y); + double ln1dx = static_cast(ln1a.x - ln1b.x); + double ln2dy = static_cast(ln2b.y - ln2a.y); + double ln2dx = static_cast(ln2a.x - ln2b.x); + double det = (ln2dy * ln1dx) - (ln1dy * ln2dx); + if (det == 0.0) return false; + T bb0minx = CC_MIN(ln1a.x, ln1b.x); + T bb0miny = CC_MIN(ln1a.y, ln1b.y); + T bb0maxx = CC_MAX(ln1a.x, ln1b.x); + T bb0maxy = CC_MAX(ln1a.y, ln1b.y); + T bb1minx = CC_MIN(ln2a.x, ln2b.x); + T bb1miny = CC_MIN(ln2a.y, ln2b.y); + T bb1maxx = CC_MAX(ln2a.x, ln2b.x); + T bb1maxy = CC_MAX(ln2a.y, ln2b.y); + + if constexpr (std::is_integral_v) + { + int64_t originx = (CC_MIN(bb0maxx, bb1maxx) + CC_MAX(bb0minx, bb1minx)) >> 1; + int64_t originy = (CC_MIN(bb0maxy, bb1maxy) + CC_MAX(bb0miny, bb1miny)) >> 1; + double ln0c = (ln1dy * static_cast(ln1a.x - originx)) + + (ln1dx * static_cast(ln1a.y - originy)); + double ln1c = (ln2dy * static_cast(ln2a.x - originx)) + + (ln2dx * static_cast(ln2a.y - originy)); + double hitx = ((ln1dx * ln1c) - (ln2dx * ln0c)) / det; + double hity = ((ln2dy * ln0c) - (ln1dy * ln1c)) / det; + + ip.x = originx + (T)nearbyint(hitx); + ip.y = originy + (T)nearbyint(hity); + } + else + { + double originx = (CC_MIN(bb0maxx, bb1maxx) + CC_MAX(bb0minx, bb1minx)) / 2.0; + double originy = (CC_MIN(bb0maxy, bb1maxy) + CC_MAX(bb0miny, bb1miny)) / 2.0; + double ln0c = (ln1dy * static_cast(ln1a.x - originx)) + + (ln1dx * static_cast(ln1a.y - originy)); + double ln1c = (ln2dy * static_cast(ln2a.x - originx)) + + (ln2dx * static_cast(ln2a.y - originy)); + double hitx = ((ln1dx * ln1c) - (ln2dx * ln0c)) / det; + double hity = ((ln2dy * ln0c) - (ln1dy * ln1c)) / det; + + ip.x = originx + static_cast(hitx); + ip.y = originy + static_cast(hity); + } + return true; +} +#else + template + inline bool GetSegmentIntersectPt(const Point& ln1a, const Point& ln1b, + const Point& ln2a, const Point& ln2b, Point& ip) + { + // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection + double dx1 = static_cast(ln1b.x - ln1a.x); + double dy1 = static_cast(ln1b.y - ln1a.y); + double dx2 = static_cast(ln2b.x - ln2a.x); + double dy2 = static_cast(ln2b.y - ln2a.y); + + double det = dy1 * dx2 - dy2 * dx1; + if (det == 0.0) return false; + double t = ((ln1a.x - ln2a.x) * dy2 - (ln1a.y - ln2a.y) * dx2) / det; + if (t <= 0.0) ip = ln1a; + else if (t >= 1.0) ip = ln1b; + else + { + ip.x = static_cast(ln1a.x + t * dx1); + ip.y = static_cast(ln1a.y + t * dy1); + } + return true; + } +#endif + + template + inline Point TranslatePoint(const Point& pt, double dx, double dy) + { +#ifdef USINGZ + return Point(pt.x + dx, pt.y + dy, pt.z); +#else + return Point(pt.x + dx, pt.y + dy); +#endif + } + + + template + inline Point ReflectPoint(const Point& pt, const Point& pivot) + { +#ifdef USINGZ + return Point(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y), pt.z); +#else + return Point(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y)); +#endif + } + + template + inline int GetSign(const T& val) + { + if (!val) return 0; + return (val > 0) ? 1 : -1; + } + + inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b, + const Point64& seg2a, const Point64& seg2b, bool inclusive = false) + { + if (inclusive) + { + double res1 = CrossProduct(seg1a, seg2a, seg2b); + double res2 = CrossProduct(seg1b, seg2a, seg2b); + if (res1 * res2 > 0) return false; + double res3 = CrossProduct(seg2a, seg1a, seg1b); + double res4 = CrossProduct(seg2b, seg1a, seg1b); + if (res3 * res4 > 0) return false; + return (res1 || res2 || res3 || res4); // ensures not collinear + } + else { + return (GetSign(CrossProduct(seg1a, seg2a, seg2b)) * + GetSign(CrossProduct(seg1b, seg2a, seg2b)) < 0) && + (GetSign(CrossProduct(seg2a, seg1a, seg1b)) * + GetSign(CrossProduct(seg2b, seg1a, seg1b)) < 0); + } + } + + template + inline Point GetClosestPointOnSegment(const Point& offPt, + const Point& seg1, const Point& seg2) + { + if (seg1.x == seg2.x && seg1.y == seg2.y) return seg1; + double dx = static_cast(seg2.x - seg1.x); + double dy = static_cast(seg2.y - seg1.y); + double q = + (static_cast(offPt.x - seg1.x) * dx + + static_cast(offPt.y - seg1.y) * dy) / + (Sqr(dx) + Sqr(dy)); + if (q < 0) q = 0; else if (q > 1) q = 1; + if constexpr (std::is_integral_v) + return Point( + seg1.x + static_cast(nearbyint(q * dx)), + seg1.y + static_cast(nearbyint(q * dy))); + else + return Point( + seg1.x + static_cast(q * dx), + seg1.y + static_cast(q * dy)); + } + + enum class PointInPolygonResult { IsOn, IsInside, IsOutside }; + + template + inline PointInPolygonResult PointInPolygon(const Point& pt, const Path& polygon) + { + if (polygon.size() < 3) + return PointInPolygonResult::IsOutside; + + int val = 0; + typename Path::const_iterator cbegin = polygon.cbegin(), first = cbegin, curr, prev; + typename Path::const_iterator cend = polygon.cend(); + + while (first != cend && first->y == pt.y) ++first; + if (first == cend) // not a proper polygon + return PointInPolygonResult::IsOutside; + + bool is_above = first->y < pt.y, starting_above = is_above; + curr = first +1; + while (true) + { + if (curr == cend) + { + if (cend == first || first == cbegin) break; + cend = first; + curr = cbegin; + } + + if (is_above) + { + while (curr != cend && curr->y < pt.y) ++curr; + if (curr == cend) continue; + } + else + { + while (curr != cend && curr->y > pt.y) ++curr; + if (curr == cend) continue; + } + + if (curr == cbegin) + prev = polygon.cend() - 1; //nb: NOT cend (since might equal first) + else + prev = curr - 1; + + if (curr->y == pt.y) + { + if (curr->x == pt.x || + (curr->y == prev->y && + ((pt.x < prev->x) != (pt.x < curr->x)))) + return PointInPolygonResult::IsOn; + ++curr; + if (curr == first) break; + continue; + } + + if (pt.x < curr->x && pt.x < prev->x) + { + // we're only interested in edges crossing on the left + } + else if (pt.x > prev->x && pt.x > curr->x) + val = 1 - val; // toggle val + else + { + double d = CrossProduct(*prev, *curr, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + is_above = !is_above; + ++curr; + } + + if (is_above != starting_above) + { + cend = polygon.cend(); + if (curr == cend) curr = cbegin; + if (curr == cbegin) prev = cend - 1; + else prev = curr - 1; + double d = CrossProduct(*prev, *curr, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + + return (val == 0) ? + PointInPolygonResult::IsOutside : + PointInPolygonResult::IsInside; + } + +} // namespace + +#endif // CLIPPER_CORE_H diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h new file mode 100644 index 0000000000..8387d2337d --- /dev/null +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h @@ -0,0 +1,646 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 17 September 2024 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2024 * +* Purpose : This is the main polygon clipping module * +* License : https://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#ifndef CLIPPER_ENGINE_H +#define CLIPPER_ENGINE_H + +#include "clipper2/clipper.core.h" +#include +#include +#include + +#ifdef USINGZ +namespace Clipper2Lib_Z { +#else +namespace Clipper2Lib { +#endif + + struct Scanline; + struct IntersectNode; + struct Active; + struct Vertex; + struct LocalMinima; + struct OutRec; + struct HorzSegment; + + //Note: all clipping operations except for Difference are commutative. + enum class ClipType { NoClip, Intersection, Union, Difference, Xor }; + + enum class PathType { Subject, Clip }; + enum class JoinWith { NoJoin, Left, Right }; + + enum class VertexFlags : uint32_t { + Empty = 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::Empty; + }; + + struct OutPt { + Point64 pt; + OutPt* next = nullptr; + OutPt* prev = nullptr; + OutRec* outrec; + HorzSegment* horz = 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 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; + Active* front_edge = nullptr; + Active* back_edge = nullptr; + OutPt* pts = nullptr; + PolyPath* polypath = nullptr; + OutRecList* splits = nullptr; + OutRec* recursive_split = nullptr; + Rect64 bounds = {}; + Path64 path; + bool is_open = false; + + ~OutRec() { + if (splits) delete splits; + // nb: don't delete the split pointers + // as these are owned by ClipperBase's outrec_list_ + }; + }; + + /////////////////////////////////////////////////////////////////// + //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; + JoinWith join_with = JoinWith::NoJoin; + }; + + 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) {} + }; + + struct HorzSegment { + OutPt* left_op; + OutPt* right_op = nullptr; + bool left_to_right = true; + HorzSegment() : left_op(nullptr) { } + explicit HorzSegment(OutPt* op) : left_op(op) { } + }; + + struct HorzJoin { + OutPt* op1 = nullptr; + OutPt* op2 = nullptr; + HorzJoin() {}; + explicit HorzJoin(OutPt* ltr, OutPt* rtl) : op1(ltr), op2(rtl) { } + }; + +#ifdef USINGZ + typedef std::function ZCallback64; + + typedef std::function ZCallbackD; +#endif + + typedef std::vector HorzSegmentList; + typedef std::unique_ptr LocalMinima_ptr; + typedef std::vector LocalMinimaList; + typedef std::vector IntersectNodeList; + + // ReuseableDataContainer64 ------------------------------------------------ + + class ReuseableDataContainer64 { + private: + friend class ClipperBase; + LocalMinimaList minima_list_; + std::vector vertex_lists_; + void AddLocMin(Vertex& vert, PathType polytype, bool is_open); + public: + virtual ~ReuseableDataContainer64(); + void Clear(); + void AddPaths(const Paths64& paths, PathType polytype, bool is_open); + }; + + // ClipperBase ------------------------------------------------------------- + + class ClipperBase { + private: + ClipType cliptype_ = ClipType::NoClip; + 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; + LocalMinimaList minima_list_; //pointers in case of memory reallocs + LocalMinimaList::iterator current_locmin_iter_; + std::vector vertex_lists_; + std::priority_queue scanline_list_; + IntersectNodeList intersect_nodes_; + HorzSegmentList horz_seg_list_; + std::vector horz_join_list_; + void Reset(); + inline void InsertScanline(int64_t y); + inline bool PopScanline(int64_t &y); + inline bool PopLocalMinima(int64_t y, LocalMinima*& local_minima); + void DisposeAllOutRecs(); + void DisposeVerticesAndLocalMinima(); + void DeleteEdges(Active*& e); + inline 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); + void 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); + OutRec* NewOutRec(); + 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 Vertex* max_vertex, + 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 FixSelfIntersects(OutRec* outrec); + void DoSplitOp(OutRec* outRec, OutPt* splitOp); + + inline void AddTrialHorzJoin(OutPt* op); + void ConvertHorzSegsToJoins(); + void ProcessHorzJoins(); + + void Split(Active& e, const Point64& pt); + inline void CheckJoinLeft(Active& e, + const Point64& pt, bool check_curr_x = false); + inline void CheckJoinRight(Active& e, + const Point64& pt, bool check_curr_x = false); + protected: + bool preserve_collinear_ = true; + bool reverse_solution_ = false; + int error_code_ = 0; + bool has_open_paths_ = false; + bool succeeded_ = true; + OutRecList outrec_list_; //pointers in case list memory reallocated + bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees); + void CleanCollinear(OutRec* outrec); + bool CheckBounds(OutRec* outrec); + bool CheckSplitOwner(OutRec* outrec, OutRecList* splits); + void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath); +#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(); + int ErrorCode() const { return error_code_; }; + void PreserveCollinear(bool val) { preserve_collinear_ = val; }; + bool PreserveCollinear() const { return preserve_collinear_;}; + void ReverseSolution(bool val) { reverse_solution_ = val; }; + bool ReverseSolution() const { return reverse_solution_; }; + void Clear(); + void AddReuseableData(const ReuseableDataContainer64& reuseable_data); +#ifdef USINGZ + int64_t DefaultZ = 0; +#endif + }; + + // 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() {}; + //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() = 0; + virtual size_t Count() const { return 0; } + + const PolyPath* Parent() const { return parent_; } + + bool IsHole() const + { + unsigned lvl = Level(); + //Even levels except level 0 + return lvl && !(lvl & 1); + } + template + static double Clipper2LibArea(const Path &poly) + { +#ifdef USINGZ + return Clipper2Lib_Z::Area(poly); +#else + return Clipper2Lib::Area(poly); +#endif + } + }; + + typedef typename std::vector> PolyPath64List; + typedef typename std::vector> PolyPathDList; + + class PolyPath64 : public PolyPath { + private: + PolyPath64List childs_; + Path64 polygon_; + public: + explicit PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {} + explicit PolyPath64(PolyPath64* parent, const Path64& path) : PolyPath(parent) { polygon_ = path; } + + ~PolyPath64() { + childs_.resize(0); + } + + PolyPath64* operator [] (size_t index) const + { + return childs_[index].get(); //std::unique_ptr + } + + PolyPath64* Child(size_t index) const + { + return childs_[index].get(); + } + + PolyPath64List::const_iterator begin() const { return childs_.cbegin(); } + PolyPath64List::const_iterator end() const { return childs_.cend(); } + + PolyPath64* AddChild(const Path64& path) override + { + return childs_.emplace_back(std::make_unique(this, path)).get(); + } + + void Clear() override + { + childs_.resize(0); + } + + size_t Count() const override + { + return childs_.size(); + } + + const Path64& Polygon() const { return polygon_; }; + + double Area() const + { + return std::accumulate(childs_.cbegin(), childs_.cend(), Clipper2LibArea(polygon_), + [](double a, const auto& child) {return a + child->Area(); }); + } + + }; + + class PolyPathD : public PolyPath { + private: + PolyPathDList childs_; + double scale_; + PathD polygon_; + public: + explicit PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent) + { + scale_ = parent ? parent->scale_ : 1.0; + } + + explicit PolyPathD(PolyPathD* parent, const Path64& path) : PolyPath(parent) + { + scale_ = parent ? parent->scale_ : 1.0; + int error_code = 0; + polygon_ = ScalePath(path, scale_, error_code); + } + + explicit PolyPathD(PolyPathD* parent, const PathD& path) : PolyPath(parent) + { + scale_ = parent ? parent->scale_ : 1.0; + polygon_ = path; + } + + ~PolyPathD() { + childs_.resize(0); + } + + PolyPathD* operator [] (size_t index) const + { + return childs_[index].get(); + } + + PolyPathD* Child(size_t index) const + { + return childs_[index].get(); + } + + PolyPathDList::const_iterator begin() const { return childs_.cbegin(); } + PolyPathDList::const_iterator end() const { return childs_.cend(); } + + void SetScale(double value) { scale_ = value; } + double Scale() const { return scale_; } + + PolyPathD* AddChild(const Path64& path) override + { + return childs_.emplace_back(std::make_unique(this, path)).get(); + } + + PolyPathD* AddChild(const PathD& path) + { + return childs_.emplace_back(std::make_unique(this, path)).get(); + } + + void Clear() override + { + childs_.resize(0); + } + + size_t Count() const override + { + return childs_.size(); + } + + const PathD& Polygon() const { return polygon_; }; + + double Area() const + { + return std::accumulate(childs_.begin(), childs_.end(), Clipper2LibArea(polygon_), + [](double a, const auto& child) {return a + child->Area(); }); + } + }; + + 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 zCallbackD_ = nullptr; +#endif + void BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen); + void BuildTreeD(PolyPathD& polytree, PathsD& open_paths); + public: + explicit ClipperD(int precision = 2) : ClipperBase() + { + CheckPrecisionRange(precision, error_code_); + // to optimize scaling / descaling precision + // set the scale to a power of double's radix (2) (#25) + scale_ = std::pow(std::numeric_limits::radix, + std::ilogb(std::pow(10, precision)) + 1); + invScale_ = 1 / scale_; + } + +#ifdef USINGZ + void SetZCallback(ZCallbackD cb) { zCallbackD_ = 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_; + zCallbackD_(e1b,e1t, e2b, e2t, tmp); + pt.z = tmp.z; // only update 'z' + }; + + void CheckCallback() + { + if(zCallbackD_) + // 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(subjects, scale_, error_code_), PathType::Subject, false); + } + + void AddOpenSubject(const PathsD& open_subjects) + { + AddPaths(ScalePaths(open_subjects, scale_, error_code_), PathType::Subject, true); + } + + void AddClip(const PathsD& clips) + { + AddPaths(ScalePaths(clips, scale_, error_code_), 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.SetScale(invScale_); + open_paths.clear(); + BuildTreeD(polytree, open_paths); + } + CleanUp(); + return succeeded_; + } + + }; + +} // namespace + +#endif // CLIPPER_ENGINE_H diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.export.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.export.h new file mode 100644 index 0000000000..a0d33d51df --- /dev/null +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.export.h @@ -0,0 +1,836 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 24 January 2025 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2025 * +* Purpose : This module exports the Clipper2 Library (ie DLL/so) * +* License : https://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + + +/* + Boolean clipping: + cliptype: NoClip=0, Intersection=1, Union=2, Difference=3, Xor=4 + fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3 + + Polygon offsetting (inflate/deflate): + jointype: Square=0, Bevel=1, Round=2, Miter=3 + endtype: Polygon=0, Joined=1, Butt=2, Square=3, Round=4 + +The path structures used extensively in other parts of this library are all +based on std::vector classes. Since C++ classes can't be accessed by other +languages, these paths are exported here as very simple array structures +(either of int64_t or double) that can be parsed by just about any +programming language. + +These 2D paths are defined by series of x and y coordinates together with an +optional user-defined 'z' value (see Z-values below). Hence, a vertex refers +to a single x and y coordinate (+/- a user-defined value). Data structures +have names with suffixes that indicate the array type (either int64_t or +double). For example, the data structure CPath64 contains an array of int64_t +values, whereas the data structure CPathD contains an array of double. +Where documentation omits the type suffix (eg CPath), it is referring to an +array whose data type could be either int64_t or double. + +For conciseness, the following letters are used in the diagrams below: +N: Number of vertices in a given path +C: Count (ie number) of paths (or PolyPaths) in the structure +A: Number of elements in an array + + +CPath64 and CPathD: +These are arrays of either int64_t or double values. Apart from +the first two elements, these arrays are a series of vertices +that together define a path. The very first element contains the +number of vertices (N) in the path, while second element should +contain a 0 value. +_______________________________________________________________ +| counters | vertex1 | vertex2 | ... | vertexN | +| N, 0 | x1, y1, (z1) | x2, y2, (z2) | ... | xN, yN, (zN) | +--------------------------------------------------------------- + + +CPaths64 and CPathsD: +These are also arrays of either int64_t or double values that +contain any number of consecutive CPath structures. However, +preceding the first path is a pair of values. The first value +contains the length of the entire array structure (A), and the +second contains the number (ie count) of contained paths (C). + Memory allocation for CPaths64 = A * sizeof(int64_t) + Memory allocation for CPathsD = A * sizeof(double) +__________________________________________ +| counters | path1 | path2 | ... | pathC | +| A, C | | | ... | | +------------------------------------------ + + +CPolytree64 and CPolytreeD: +The entire polytree structure is an array of int64_t or double. The +first element in the array indicates the array's total length (A). +The second element indicates the number (C) of CPolyPath structures +that are the TOP LEVEL CPolyPath in the polytree, and these top +level CPolyPath immediately follow these first two array elements. +These top level CPolyPath structures may, in turn, contain nested +CPolyPath children, and these collectively make a tree structure. +_________________________________________________________ +| counters | CPolyPath1 | CPolyPath2 | ... | CPolyPathC | +| A, C | | | ... | | +--------------------------------------------------------- + + +CPolyPath64 and CPolyPathD: +These array structures consist of a pair of counter values followed by a +series of polygon vertices and a series of nested CPolyPath children. +The first counter values indicates the number of vertices in the +polygon (N), and the second counter indicates the CPolyPath child count (C). +_____________________________________________________________________________ +|cntrs |vertex1 |vertex2 |...|vertexN |child1|child2|...|childC| +|N, C |x1, y1, (z1)| x2, y2, (z2)|...|xN, yN, (zN)| | |...| | +----------------------------------------------------------------------------- + + +DisposeArray64 & DisposeArrayD: +All array structures are allocated in heap memory which will eventually +need to be released. However, since applications linking to these DLL +functions may use different memory managers, the only safe way to release +this memory is to use the exported DisposeArray functions. + + +(Optional) Z-Values: +Structures will only contain user-defined z-values when the USINGZ +pre-processor identifier is used. The library does not assign z-values +because this field is intended for users to assign custom values to vertices. +Z-values in input paths (subject and clip) will be copied to solution paths. +New vertices at path intersections will generate a callback event that allows +users to assign z-values at these new vertices. The user's callback function +must conform with the DLLZCallback definition and be registered with the +DLL via SetZCallback. To assist the user in assigning z-values, the library +passes in the callback function the new intersection point together with +the four vertices that define the two segments that are intersecting. + +*/ +#ifndef CLIPPER2_EXPORT_H +#define CLIPPER2_EXPORT_H + +#include "clipper2/clipper.core.h" +#include "clipper2/clipper.engine.h" +#include "clipper2/clipper.offset.h" +#include "clipper2/clipper.rectclip.h" +#include + +#ifdef USINGZ +namespace Clipper2Lib_Z { +#else +namespace Clipper2Lib { +#endif + +typedef int64_t* CPath64; +typedef int64_t* CPaths64; +typedef double* CPathD; +typedef double* CPathsD; + +typedef int64_t* CPolyPath64; +typedef int64_t* CPolyTree64; +typedef double* CPolyPathD; +typedef double* CPolyTreeD; + +template +struct CRect { + T left; + T top; + T right; + T bottom; +}; + +typedef CRect CRect64; +typedef CRect CRectD; + +template +inline bool CRectIsEmpty(const CRect& rect) +{ + return (rect.right <= rect.left) || (rect.bottom <= rect.top); +} + +template +inline Rect CRectToRect(const CRect& rect) +{ + Rect result; + result.left = rect.left; + result.top = rect.top; + result.right = rect.right; + result.bottom = rect.bottom; + return result; +} + +template +inline T1 Reinterpret(T2 value) { + return *reinterpret_cast(&value); +} + + +#ifdef _WIN32 + #define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport) +#else + #define EXTERN_DLL_EXPORT extern "C" +#endif + + +////////////////////////////////////////////////////// +// EXPORTED FUNCTION DECLARATIONS +////////////////////////////////////////////////////// + +EXTERN_DLL_EXPORT const char* Version(); + +EXTERN_DLL_EXPORT void DisposeArray64(int64_t*& p) +{ + delete[] p; +} + +EXTERN_DLL_EXPORT void DisposeArrayD(double*& p) +{ + delete[] p; +} + +EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, + uint8_t fillrule, const CPaths64 subjects, + const CPaths64 subjects_open, const CPaths64 clips, + CPaths64& solution, CPaths64& solution_open, + bool preserve_collinear = true, bool reverse_solution = false); + +EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype, + uint8_t fillrule, const CPaths64 subjects, + const CPaths64 subjects_open, const CPaths64 clips, + CPolyTree64& sol_tree, CPaths64& solution_open, + bool preserve_collinear = true, bool reverse_solution = false); + +EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, + uint8_t fillrule, const CPathsD subjects, + const CPathsD subjects_open, const CPathsD clips, + CPathsD& solution, CPathsD& solution_open, int precision = 2, + bool preserve_collinear = true, bool reverse_solution = false); + +EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype, + uint8_t fillrule, const CPathsD subjects, + const CPathsD subjects_open, const CPathsD clips, + CPolyTreeD& solution, CPathsD& solution_open, int precision = 2, + bool preserve_collinear = true, bool reverse_solution = false); + +EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths, + double delta, uint8_t jointype, uint8_t endtype, + double miter_limit = 2.0, double arc_tolerance = 0.0, + bool reverse_solution = false); + +EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, + double delta, uint8_t jointype, uint8_t endtype, + int precision = 2, double miter_limit = 2.0, + double arc_tolerance = 0.0, bool reverse_solution = false); + +EXTERN_DLL_EXPORT CPaths64 InflatePath64(const CPath64 path, + double delta, uint8_t jointype, uint8_t endtype, + double miter_limit = 2.0, double arc_tolerance = 0.0, + bool reverse_solution = false); + +EXTERN_DLL_EXPORT CPathsD InflatePathD(const CPathD path, + double delta, uint8_t jointype, uint8_t endtype, + int precision = 2, double miter_limit = 2.0, + double arc_tolerance = 0.0, bool reverse_solution = false); + +// RectClip & RectClipLines: +EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, + const CPaths64 paths); +EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, + const CPathsD paths, int precision = 2); +EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, + const CPaths64 paths); +EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, + const CPathsD paths, int precision = 2); + +////////////////////////////////////////////////////// +// INTERNAL FUNCTIONS +////////////////////////////////////////////////////// + +#ifdef USINGZ +ZCallback64 dllCallback64 = nullptr; +ZCallbackD dllCallbackD = nullptr; + +constexpr int EXPORT_VERTEX_DIMENSIONALITY = 3; +#else +constexpr int EXPORT_VERTEX_DIMENSIONALITY = 2; +#endif + +template +static void GetPathCountAndCPathsArrayLen(const Paths& paths, + size_t& cnt, size_t& array_len) +{ + array_len = 2; + cnt = 0; + for (const Path& path : paths) + if (path.size()) + { + array_len += path.size() * EXPORT_VERTEX_DIMENSIONALITY + 2; + ++cnt; + } +} + +static size_t GetPolyPathArrayLen64(const PolyPath64& pp) +{ + size_t result = 2; // poly_length + child_count + result += pp.Polygon().size() * EXPORT_VERTEX_DIMENSIONALITY; + //plus nested children :) + for (size_t i = 0; i < pp.Count(); ++i) + result += GetPolyPathArrayLen64(*pp[i]); + return result; +} + +static size_t GetPolyPathArrayLenD(const PolyPathD& pp) +{ + size_t result = 2; // poly_length + child_count + result += pp.Polygon().size() * EXPORT_VERTEX_DIMENSIONALITY; + //plus nested children :) + for (size_t i = 0; i < pp.Count(); ++i) + result += GetPolyPathArrayLenD(*pp[i]); + return result; +} + +static void GetPolytreeCountAndCStorageSize64(const PolyTree64& tree, + size_t& cnt, size_t& array_len) +{ + cnt = tree.Count(); // nb: top level count only + array_len = GetPolyPathArrayLen64(tree); +} + +static void GetPolytreeCountAndCStorageSizeD(const PolyTreeD& tree, + size_t& cnt, size_t& array_len) +{ + cnt = tree.Count(); // nb: top level count only + array_len = GetPolyPathArrayLenD(tree); +} + +template +static T* CreateCPathsFromPathsT(const Paths& paths) +{ + size_t cnt = 0, array_len = 0; + GetPathCountAndCPathsArrayLen(paths, cnt, array_len); + T* result = new T[array_len], * v = result; + *v++ = array_len; + *v++ = cnt; + for (const Path& path : paths) + { + if (!path.size()) continue; + *v++ = path.size(); + *v++ = 0; + for (const Point& pt : path) + { + *v++ = pt.x; + *v++ = pt.y; +#ifdef USINGZ + *v++ = Reinterpret(pt.z); +#endif + } + } + return result; +} + +CPathsD CreateCPathsDFromPathsD(const PathsD& paths) +{ + if (!paths.size()) return nullptr; + size_t cnt, array_len; + GetPathCountAndCPathsArrayLen(paths, cnt, array_len); + CPathsD result = new double[array_len], v = result; + *v++ = (double)array_len; + *v++ = (double)cnt; + for (const PathD& path : paths) + { + if (!path.size()) continue; + *v = (double)path.size(); + ++v; *v++ = 0; + for (const PointD& pt : path) + { + *v++ = pt.x; + *v++ = pt.y; +#ifdef USINGZ + * v++ = Reinterpret(pt.z); +#endif + } + } + return result; +} + +CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale) +{ + if (!paths.size()) return nullptr; + size_t cnt, array_len; + GetPathCountAndCPathsArrayLen(paths, cnt, array_len); + CPathsD result = new double[array_len], v = result; + *v++ = (double)array_len; + *v++ = (double)cnt; + for (const Path64& path : paths) + { + if (!path.size()) continue; + *v = (double)path.size(); + ++v; *v++ = 0; + for (const Point64& pt : path) + { + *v++ = pt.x * scale; + *v++ = pt.y * scale; +#ifdef USINGZ + *v++ = Reinterpret(pt.z); +#endif + } + } + return result; +} + +template +static Path ConvertCPathToPathT(T* path) +{ + Path result; + if (!path) return result; + T* v = path; + size_t cnt = static_cast(*v); + v += 2; // skip 0 value + result.reserve(cnt); + for (size_t j = 0; j < cnt; ++j) + { + T x = *v++, y = *v++; +#ifdef USINGZ + z_type z = Reinterpret(*v++); + result.emplace_back(x, y, z); +#else + result.emplace_back(x, y); +#endif + } + return result; +} + +template +static Paths ConvertCPathsToPathsT(T* paths) +{ + Paths result; + if (!paths) return result; + T* v = paths; ++v; + size_t cnt = static_cast(*v++); + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + { + size_t cnt2 = static_cast(*v); + v += 2; + Path path; + path.reserve(cnt2); + for (size_t j = 0; j < cnt2; ++j) + { + T x = *v++, y = *v++; +#ifdef USINGZ + z_type z = Reinterpret(*v++); + path.emplace_back(x, y, z); +#else + path.emplace_back(x, y); +#endif + } + result.emplace_back(std::move(path)); + } + return result; +} + +static Path64 ConvertCPathDToPath64WithScale(const CPathD path, double scale) +{ + Path64 result; + if (!path) return result; + double* v = path; + size_t cnt = static_cast(*v); + v += 2; // skip 0 value + result.reserve(cnt); + for (size_t j = 0; j < cnt; ++j) + { + double x = *v++ * scale; + double y = *v++ * scale; +#ifdef USINGZ + z_type z = Reinterpret(*v++); + result.emplace_back(x, y, z); +#else + result.emplace_back(x, y); +#endif + } + return result; +} + +static Paths64 ConvertCPathsDToPaths64(const CPathsD paths, double scale) +{ + Paths64 result; + if (!paths) return result; + double* v = paths; + ++v; // skip the first value (0) + size_t cnt = static_cast(*v++); + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + { + size_t cnt2 = static_cast(*v); + v += 2; + Path64 path; + path.reserve(cnt2); + for (size_t j = 0; j < cnt2; ++j) + { + double x = *v++ * scale; + double y = *v++ * scale; +#ifdef USINGZ + z_type z = Reinterpret(*v++); + path.emplace_back(x, y, z); +#else + path.emplace_back(x, y); +#endif + } + result.emplace_back(std::move(path)); + } + return result; +} + +static void CreateCPolyPath64(const PolyPath64* pp, int64_t*& v) +{ + *v++ = static_cast(pp->Polygon().size()); + *v++ = static_cast(pp->Count()); + for (const Point64& pt : pp->Polygon()) + { + *v++ = pt.x; + *v++ = pt.y; +#ifdef USINGZ + * v++ = Reinterpret(pt.z); // raw memory copy +#endif + } + for (size_t i = 0; i < pp->Count(); ++i) + CreateCPolyPath64(pp->Child(i), v); +} + +static void CreateCPolyPathD(const PolyPathD* pp, double*& v) +{ + *v++ = static_cast(pp->Polygon().size()); + *v++ = static_cast(pp->Count()); + for (const PointD& pt : pp->Polygon()) + { + *v++ = pt.x; + *v++ = pt.y; +#ifdef USINGZ + * v++ = Reinterpret(pt.z); // raw memory copy +#endif + } + for (size_t i = 0; i < pp->Count(); ++i) + CreateCPolyPathD(pp->Child(i), v); +} + +static int64_t* CreateCPolyTree64(const PolyTree64& tree) +{ + size_t cnt, array_len; + GetPolytreeCountAndCStorageSize64(tree, cnt, array_len); + if (!cnt) return nullptr; + // allocate storage + int64_t* result = new int64_t[array_len]; + int64_t* v = result; + *v++ = static_cast(array_len); + *v++ = static_cast(tree.Count()); + for (size_t i = 0; i < tree.Count(); ++i) + CreateCPolyPath64(tree.Child(i), v); + return result; +} + +static double* CreateCPolyTreeD(const PolyTreeD& tree) +{ + double scale = std::log10(tree.Scale()); + size_t cnt, array_len; + GetPolytreeCountAndCStorageSizeD(tree, cnt, array_len); + if (!cnt) return nullptr; + // allocate storage + double* result = new double[array_len]; + double* v = result; + *v++ = static_cast(array_len); + *v++ = static_cast(tree.Count()); + for (size_t i = 0; i < tree.Count(); ++i) + CreateCPolyPathD(tree.Child(i), v); + return result; +} + +////////////////////////////////////////////////////// +// EXPORTED FUNCTION DEFINITIONS +////////////////////////////////////////////////////// + +EXTERN_DLL_EXPORT const char* Version() +{ + return CLIPPER2_VERSION; +} + +EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, + uint8_t fillrule, const CPaths64 subjects, + const CPaths64 subjects_open, const CPaths64 clips, + CPaths64& solution, CPaths64& solution_open, + bool preserve_collinear, bool reverse_solution) +{ + if (cliptype > static_cast(ClipType::Xor)) return -4; + if (fillrule > static_cast(FillRule::Negative)) return -3; + + Paths64 sub, sub_open, clp, sol, sol_open; + sub = ConvertCPathsToPathsT(subjects); + sub_open = ConvertCPathsToPathsT(subjects_open); + clp = ConvertCPathsToPathsT(clips); + + Clipper64 clipper; + clipper.PreserveCollinear(preserve_collinear); + clipper.ReverseSolution(reverse_solution); +#ifdef USINGZ + if (dllCallback64) + clipper.SetZCallback(dllCallback64); +#endif + if (sub.size() > 0) clipper.AddSubject(sub); + if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); + if (clp.size() > 0) clipper.AddClip(clp); + if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open)) + return -1; // clipping bug - should never happen :) + solution = CreateCPathsFromPathsT(sol); + solution_open = CreateCPathsFromPathsT(sol_open); + return 0; //success !! +} + +EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype, + uint8_t fillrule, const CPaths64 subjects, + const CPaths64 subjects_open, const CPaths64 clips, + CPolyTree64& sol_tree, CPaths64& solution_open, + bool preserve_collinear, bool reverse_solution) +{ + if (cliptype > static_cast(ClipType::Xor)) return -4; + if (fillrule > static_cast(FillRule::Negative)) return -3; + Paths64 sub, sub_open, clp, sol_open; + sub = ConvertCPathsToPathsT(subjects); + sub_open = ConvertCPathsToPathsT(subjects_open); + clp = ConvertCPathsToPathsT(clips); + + PolyTree64 tree; + Clipper64 clipper; + clipper.PreserveCollinear(preserve_collinear); + clipper.ReverseSolution(reverse_solution); +#ifdef USINGZ + if (dllCallback64) + clipper.SetZCallback(dllCallback64); +#endif + if (sub.size() > 0) clipper.AddSubject(sub); + if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); + if (clp.size() > 0) clipper.AddClip(clp); + if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open)) + return -1; // clipping bug - should never happen :) + + sol_tree = CreateCPolyTree64(tree); + solution_open = CreateCPathsFromPathsT(sol_open); + return 0; //success !! +} + +EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, + uint8_t fillrule, const CPathsD subjects, + const CPathsD subjects_open, const CPathsD clips, + CPathsD& solution, CPathsD& solution_open, int precision, + bool preserve_collinear, bool reverse_solution) +{ + if (precision < -8 || precision > 8) return -5; + if (cliptype > static_cast(ClipType::Xor)) return -4; + if (fillrule > static_cast(FillRule::Negative)) return -3; + //const double scale = std::pow(10, precision); + + PathsD sub, sub_open, clp, sol, sol_open; + sub = ConvertCPathsToPathsT(subjects); + sub_open = ConvertCPathsToPathsT(subjects_open); + clp = ConvertCPathsToPathsT(clips); + + ClipperD clipper(precision); + clipper.PreserveCollinear(preserve_collinear); + clipper.ReverseSolution(reverse_solution); +#ifdef USINGZ + if (dllCallbackD) + clipper.SetZCallback(dllCallbackD); +#endif + if (sub.size() > 0) clipper.AddSubject(sub); + if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); + if (clp.size() > 0) clipper.AddClip(clp); + if (!clipper.Execute(ClipType(cliptype), + FillRule(fillrule), sol, sol_open)) return -1; + solution = CreateCPathsDFromPathsD(sol); + solution_open = CreateCPathsDFromPathsD(sol_open); + return 0; +} + +EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype, + uint8_t fillrule, const CPathsD subjects, + const CPathsD subjects_open, const CPathsD clips, + CPolyTreeD& solution, CPathsD& solution_open, int precision, + bool preserve_collinear, bool reverse_solution) +{ + if (precision < -8 || precision > 8) return -5; + if (cliptype > static_cast(ClipType::Xor)) return -4; + if (fillrule > static_cast(FillRule::Negative)) return -3; + //double scale = std::pow(10, precision); + + int err = 0; + PathsD sub, sub_open, clp, sol_open; + sub = ConvertCPathsToPathsT(subjects); + sub_open = ConvertCPathsToPathsT(subjects_open); + clp = ConvertCPathsToPathsT(clips); + + PolyTreeD tree; + ClipperD clipper(precision); + clipper.PreserveCollinear(preserve_collinear); + clipper.ReverseSolution(reverse_solution); +#ifdef USINGZ + if (dllCallbackD) + clipper.SetZCallback(dllCallbackD); +#endif + if (sub.size() > 0) clipper.AddSubject(sub); + if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); + if (clp.size() > 0) clipper.AddClip(clp); + if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open)) + return -1; // clipping bug - should never happen :) + + solution = CreateCPolyTreeD(tree); + solution_open = CreateCPathsDFromPathsD(sol_open); + return 0; //success !! +} + +EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths, + double delta, uint8_t jointype, uint8_t endtype, double miter_limit, + double arc_tolerance, bool reverse_solution) +{ + Paths64 pp; + pp = ConvertCPathsToPathsT(paths); + ClipperOffset clip_offset( miter_limit, + arc_tolerance, reverse_solution); + clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype)); + Paths64 result; + clip_offset.Execute(delta, result); + return CreateCPathsFromPathsT(result); +} + +EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, + double delta, uint8_t jointype, uint8_t endtype, + int precision, double miter_limit, + double arc_tolerance, bool reverse_solution) +{ + if (precision < -8 || precision > 8 || !paths) return nullptr; + + const double scale = std::pow(10, precision); + ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution); + Paths64 pp = ConvertCPathsDToPaths64(paths, scale); + clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype)); + Paths64 result; + clip_offset.Execute(delta * scale, result); + return CreateCPathsDFromPaths64(result, 1 / scale); +} + + +EXTERN_DLL_EXPORT CPaths64 InflatePath64(const CPath64 path, + double delta, uint8_t jointype, uint8_t endtype, double miter_limit, + double arc_tolerance, bool reverse_solution) +{ + Path64 pp; + pp = ConvertCPathToPathT(path); + ClipperOffset clip_offset(miter_limit, + arc_tolerance, reverse_solution); + clip_offset.AddPath(pp, JoinType(jointype), EndType(endtype)); + Paths64 result; + clip_offset.Execute(delta, result); + return CreateCPathsFromPathsT(result); +} + +EXTERN_DLL_EXPORT CPathsD InflatePathD(const CPathD path, + double delta, uint8_t jointype, uint8_t endtype, + int precision, double miter_limit, + double arc_tolerance, bool reverse_solution) +{ + if (precision < -8 || precision > 8 || !path) return nullptr; + + const double scale = std::pow(10, precision); + ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution); + Path64 pp = ConvertCPathDToPath64WithScale(path, scale); + clip_offset.AddPath(pp, JoinType(jointype), EndType(endtype)); + Paths64 result; + clip_offset.Execute(delta * scale, result); + + return CreateCPathsDFromPaths64(result, 1 / scale); +} + +EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths) +{ + if (CRectIsEmpty(rect) || !paths) return nullptr; + Rect64 r64 = CRectToRect(rect); + class RectClip64 rc(r64); + Paths64 pp = ConvertCPathsToPathsT(paths); + Paths64 result = rc.Execute(pp); + return CreateCPathsFromPathsT(result); +} + +EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int precision) +{ + if (CRectIsEmpty(rect) || !paths) return nullptr; + if (precision < -8 || precision > 8) return nullptr; + const double scale = std::pow(10, precision); + + RectD r = CRectToRect(rect); + Rect64 rec = ScaleRect(r, scale); + Paths64 pp = ConvertCPathsDToPaths64(paths, scale); + class RectClip64 rc(rec); + Paths64 result = rc.Execute(pp); + + return CreateCPathsDFromPaths64(result, 1 / scale); +} + +EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, + const CPaths64 paths) +{ + if (CRectIsEmpty(rect) || !paths) return nullptr; + Rect64 r = CRectToRect(rect); + class RectClipLines64 rcl (r); + Paths64 pp = ConvertCPathsToPathsT(paths); + Paths64 result = rcl.Execute(pp); + return CreateCPathsFromPathsT(result); +} + +EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, + const CPathsD paths, int precision) +{ + if (CRectIsEmpty(rect) || !paths) return nullptr; + if (precision < -8 || precision > 8) return nullptr; + + const double scale = std::pow(10, precision); + Rect64 r = ScaleRect(CRectToRect(rect), scale); + class RectClipLines64 rcl(r); + Paths64 pp = ConvertCPathsDToPaths64(paths, scale); + Paths64 result = rcl.Execute(pp); + return CreateCPathsDFromPaths64(result, 1 / scale); +} + +EXTERN_DLL_EXPORT CPaths64 MinkowskiSum64(const CPath64& cpattern, const CPath64& cpath, bool is_closed) +{ + Path64 path = ConvertCPathToPathT(cpath); + Path64 pattern = ConvertCPathToPathT(cpattern); + Paths64 solution = MinkowskiSum(pattern, path, is_closed); + return CreateCPathsFromPathsT(solution); +} + +EXTERN_DLL_EXPORT CPaths64 MinkowskiDiff64(const CPath64& cpattern, const CPath64& cpath, bool is_closed) +{ + Path64 path = ConvertCPathToPathT(cpath); + Path64 pattern = ConvertCPathToPathT(cpattern); + Paths64 solution = MinkowskiDiff(pattern, path, is_closed); + return CreateCPathsFromPathsT(solution); +} + +#ifdef USINGZ +typedef void (*DLLZCallback64)(const Point64& e1bot, const Point64& e1top, const Point64& e2bot, const Point64& e2top, Point64& pt); +typedef void (*DLLZCallbackD)(const PointD& e1bot, const PointD& e1top, const PointD& e2bot, const PointD& e2top, PointD& pt); + +EXTERN_DLL_EXPORT void SetZCallback64(DLLZCallback64 callback) +{ + dllCallback64 = callback; +} + +EXTERN_DLL_EXPORT void SetZCallbackD(DLLZCallbackD callback) +{ + dllCallbackD = callback; +} + +#endif + +} +#endif // CLIPPER2_EXPORT_H diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.h new file mode 100644 index 0000000000..f6301fc52e --- /dev/null +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.h @@ -0,0 +1,770 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 27 April 2024 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2024 * +* Purpose : This module provides a simple interface to the Clipper Library * +* License : https://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#ifndef CLIPPER_H +#define CLIPPER_H + +#include "clipper2/clipper.core.h" +#include "clipper2/clipper.engine.h" +#include "clipper2/clipper.offset.h" +#include "clipper2/clipper.minkowski.h" +#include "clipper2/clipper.rectclip.h" +#include + +#ifdef USINGZ + namespace Clipper2Lib_Z { +#else + namespace Clipper2Lib { +#endif + + inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule, + const Paths64& subjects, const Paths64& clips) + { + Paths64 result; + Clipper64 clipper; + clipper.AddSubject(subjects); + clipper.AddClip(clips); + clipper.Execute(cliptype, fillrule, result); + return result; + } + + inline void BooleanOp(ClipType cliptype, FillRule fillrule, + const Paths64& subjects, const Paths64& clips, PolyTree64& solution) + { + Paths64 sol_open; + Clipper64 clipper; + clipper.AddSubject(subjects); + clipper.AddClip(clips); + clipper.Execute(cliptype, fillrule, solution, sol_open); + } + + inline PathsD BooleanOp(ClipType cliptype, FillRule fillrule, + const PathsD& subjects, const PathsD& clips, int precision = 2) + { + int error_code = 0; + CheckPrecisionRange(precision, error_code); + PathsD result; + if (error_code) return result; + ClipperD clipper(precision); + clipper.AddSubject(subjects); + clipper.AddClip(clips); + clipper.Execute(cliptype, fillrule, result); + return result; + } + + inline void BooleanOp(ClipType cliptype, FillRule fillrule, + const PathsD& subjects, const PathsD& clips, + PolyTreeD& polytree, int precision = 2) + { + polytree.Clear(); + int error_code = 0; + CheckPrecisionRange(precision, error_code); + if (error_code) return; + ClipperD clipper(precision); + clipper.AddSubject(subjects); + clipper.AddClip(clips); + clipper.Execute(cliptype, fillrule, polytree); + } + + inline Paths64 Intersect(const Paths64& subjects, const Paths64& clips, FillRule fillrule) + { + return BooleanOp(ClipType::Intersection, fillrule, subjects, clips); + } + + inline PathsD Intersect(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2) + { + return BooleanOp(ClipType::Intersection, fillrule, subjects, clips, decimal_prec); + } + + inline Paths64 Union(const Paths64& subjects, const Paths64& clips, FillRule fillrule) + { + return BooleanOp(ClipType::Union, fillrule, subjects, clips); + } + + inline PathsD Union(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2) + { + return BooleanOp(ClipType::Union, fillrule, subjects, clips, decimal_prec); + } + + inline Paths64 Union(const Paths64& subjects, FillRule fillrule) + { + Paths64 result; + Clipper64 clipper; + clipper.AddSubject(subjects); + clipper.Execute(ClipType::Union, fillrule, result); + return result; + } + + inline PathsD Union(const PathsD& subjects, FillRule fillrule, int precision = 2) + { + PathsD result; + int error_code = 0; + CheckPrecisionRange(precision, error_code); + if (error_code) return result; + ClipperD clipper(precision); + clipper.AddSubject(subjects); + clipper.Execute(ClipType::Union, fillrule, result); + return result; + } + + inline Paths64 Difference(const Paths64& subjects, const Paths64& clips, FillRule fillrule) + { + return BooleanOp(ClipType::Difference, fillrule, subjects, clips); + } + + inline PathsD Difference(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2) + { + return BooleanOp(ClipType::Difference, fillrule, subjects, clips, decimal_prec); + } + + inline Paths64 Xor(const Paths64& subjects, const Paths64& clips, FillRule fillrule) + { + return BooleanOp(ClipType::Xor, fillrule, subjects, clips); + } + + inline PathsD Xor(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2) + { + return BooleanOp(ClipType::Xor, fillrule, subjects, clips, decimal_prec); + } + + inline Paths64 InflatePaths(const Paths64& paths, double delta, + JoinType jt, EndType et, double miter_limit = 2.0, + double arc_tolerance = 0.0) + { + if (!delta) return paths; + ClipperOffset clip_offset(miter_limit, arc_tolerance); + clip_offset.AddPaths(paths, jt, et); + Paths64 solution; + clip_offset.Execute(delta, solution); + return solution; + } + + inline PathsD InflatePaths(const PathsD& paths, double delta, + JoinType jt, EndType et, double miter_limit = 2.0, + int precision = 2, double arc_tolerance = 0.0) + { + int error_code = 0; + CheckPrecisionRange(precision, error_code); + if (!delta) return paths; + if (error_code) return PathsD(); + const double scale = std::pow(10, precision); + ClipperOffset clip_offset(miter_limit, arc_tolerance); + clip_offset.AddPaths(ScalePaths(paths, scale, error_code), jt, et); + if (error_code) return PathsD(); + Paths64 solution; + clip_offset.Execute(delta * scale, solution); + return ScalePaths(solution, 1 / scale, error_code); + } + + template + inline Path TranslatePath(const Path& path, T dx, T dy) + { + Path result; + result.reserve(path.size()); + std::transform(path.begin(), path.end(), back_inserter(result), + [dx, dy](const auto& pt) { return Point(pt.x + dx, pt.y +dy); }); + return result; + } + + inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy) + { + return TranslatePath(path, dx, dy); + } + + inline PathD TranslatePath(const PathD& path, double dx, double dy) + { + return TranslatePath(path, dx, dy); + } + + template + inline Paths TranslatePaths(const Paths& paths, T dx, T dy) + { + Paths result; + result.reserve(paths.size()); + std::transform(paths.begin(), paths.end(), back_inserter(result), + [dx, dy](const auto& path) { return TranslatePath(path, dx, dy); }); + return result; + } + + inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy) + { + return TranslatePaths(paths, dx, dy); + } + + inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy) + { + return TranslatePaths(paths, dx, dy); + } + + inline Paths64 RectClip(const Rect64& rect, const Paths64& paths) + { + if (rect.IsEmpty() || paths.empty()) return Paths64(); + RectClip64 rc(rect); + return rc.Execute(paths); + } + + inline Paths64 RectClip(const Rect64& rect, const Path64& path) + { + if (rect.IsEmpty() || path.empty()) return Paths64(); + RectClip64 rc(rect); + return rc.Execute(Paths64{ path }); + } + + inline PathsD RectClip(const RectD& rect, const PathsD& paths, int precision = 2) + { + if (rect.IsEmpty() || paths.empty()) return PathsD(); + int error_code = 0; + CheckPrecisionRange(precision, error_code); + if (error_code) return PathsD(); + const double scale = std::pow(10, precision); + Rect64 r = ScaleRect(rect, scale); + RectClip64 rc(r); + Paths64 pp = ScalePaths(paths, scale, error_code); + if (error_code) return PathsD(); // ie: error_code result is lost + return ScalePaths( + rc.Execute(pp), 1 / scale, error_code); + } + + inline PathsD RectClip(const RectD& rect, const PathD& path, int precision = 2) + { + return RectClip(rect, PathsD{ path }, precision); + } + + inline Paths64 RectClipLines(const Rect64& rect, const Paths64& lines) + { + if (rect.IsEmpty() || lines.empty()) return Paths64(); + RectClipLines64 rcl(rect); + return rcl.Execute(lines); + } + + inline Paths64 RectClipLines(const Rect64& rect, const Path64& line) + { + return RectClipLines(rect, Paths64{ line }); + } + + inline PathsD RectClipLines(const RectD& rect, const PathsD& lines, int precision = 2) + { + if (rect.IsEmpty() || lines.empty()) return PathsD(); + int error_code = 0; + CheckPrecisionRange(precision, error_code); + if (error_code) return PathsD(); + const double scale = std::pow(10, precision); + Rect64 r = ScaleRect(rect, scale); + RectClipLines64 rcl(r); + Paths64 p = ScalePaths(lines, scale, error_code); + if (error_code) return PathsD(); + p = rcl.Execute(p); + return ScalePaths(p, 1 / scale, error_code); + } + + inline PathsD RectClipLines(const RectD& rect, const PathD& line, int precision = 2) + { + return RectClipLines(rect, PathsD{ line }, precision); + } + + namespace details + { + + inline void PolyPathToPaths64(const PolyPath64& polypath, Paths64& paths) + { + paths.emplace_back(polypath.Polygon()); + for (const auto& child : polypath) + PolyPathToPaths64(*child, paths); + } + + inline void PolyPathToPathsD(const PolyPathD& polypath, PathsD& paths) + { + paths.emplace_back(polypath.Polygon()); + for (const auto& child : polypath) + PolyPathToPathsD(*child, paths); + } + + inline bool PolyPath64ContainsChildren(const PolyPath64& pp) + { + for (const auto& child : pp) + { + // return false if this child isn't fully contained by its parent + + // checking for a single vertex outside is a bit too crude since + // it doesn't account for rounding errors. It's better to check + // for consecutive vertices found outside the parent's polygon. + + int outsideCnt = 0; + for (const Point64& pt : child->Polygon()) + { + PointInPolygonResult result = PointInPolygon(pt, pp.Polygon()); + if (result == PointInPolygonResult::IsInside) --outsideCnt; + else if (result == PointInPolygonResult::IsOutside) ++outsideCnt; + if (outsideCnt > 1) return false; + else if (outsideCnt < -1) break; + } + + // now check any nested children too + if (child->Count() > 0 && !PolyPath64ContainsChildren(*child)) + return false; + } + return true; + } + + static void OutlinePolyPath(std::ostream& os, + size_t idx, bool isHole, size_t count, const std::string& preamble) + { + std::string plural = (count == 1) ? "." : "s."; + if (isHole) + os << preamble << "+- Hole (" << idx << ") contains " << count << + " nested polygon" << plural << std::endl; + else + os << preamble << "+- Polygon (" << idx << ") contains " << count << + " hole" << plural << std::endl; + } + + static void OutlinePolyPath64(std::ostream& os, const PolyPath64& pp, + size_t idx, std::string preamble) + { + OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble); + for (size_t i = 0; i < pp.Count(); ++i) + if (pp.Child(i)->Count()) + details::OutlinePolyPath64(os, *pp.Child(i), i, preamble + " "); + } + + static void OutlinePolyPathD(std::ostream& os, const PolyPathD& pp, + size_t idx, std::string preamble) + { + OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble); + for (size_t i = 0; i < pp.Count(); ++i) + if (pp.Child(i)->Count()) + details::OutlinePolyPathD(os, *pp.Child(i), i, preamble + " "); + } + + template + inline constexpr void MakePathGeneric(const T an_array, + size_t array_size, std::vector& result) + { + result.reserve(array_size / 2); + for (size_t i = 0; i < array_size; i +=2) +#ifdef USINGZ + result.emplace_back( an_array[i], an_array[i + 1], 0 ); +#else + result.emplace_back( an_array[i], an_array[i + 1] ); +#endif + } + + } // end details namespace + + inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp) + { + std::string plural = (pp.Count() == 1) ? " polygon." : " polygons."; + os << std::endl << "Polytree with " << pp.Count() << plural << std::endl; + for (size_t i = 0; i < pp.Count(); ++i) + if (pp.Child(i)->Count()) + details::OutlinePolyPath64(os, *pp.Child(i), i, " "); + os << std::endl << std::endl; + return os; + } + + inline std::ostream& operator<< (std::ostream& os, const PolyTreeD& pp) + { + std::string plural = (pp.Count() == 1) ? " polygon." : " polygons."; + os << std::endl << "Polytree with " << pp.Count() << plural << std::endl; + for (size_t i = 0; i < pp.Count(); ++i) + if (pp.Child(i)->Count()) + details::OutlinePolyPathD(os, *pp.Child(i), i, " "); + os << std::endl << std::endl; + if (!pp.Level()) os << std::endl; + return os; + } + + inline Paths64 PolyTreeToPaths64(const PolyTree64& polytree) + { + Paths64 result; + for (const auto& child : polytree) + details::PolyPathToPaths64(*child, result); + return result; + } + + inline PathsD PolyTreeToPathsD(const PolyTreeD& polytree) + { + PathsD result; + for (const auto& child : polytree) + details::PolyPathToPathsD(*child, result); + return result; + } + + inline bool CheckPolytreeFullyContainsChildren(const PolyTree64& polytree) + { + for (const auto& child : polytree) + if (child->Count() > 0 && + !details::PolyPath64ContainsChildren(*child)) + return false; + return true; + } + + template::value && + !std::is_same::value, bool + >::type = true> + inline Path64 MakePath(const std::vector& list) + { + const auto size = list.size() - list.size() % 2; + if (list.size() != size) + DoError(non_pair_error_i); // non-fatal without exception handling + Path64 result; + details::MakePathGeneric(list, size, result); + return result; + } + + template::value && + !std::is_same::value, bool + >::type = true> + inline Path64 MakePath(const T(&list)[N]) + { + // Make the compiler error on unpaired value (i.e. no runtime effects). + static_assert(N % 2 == 0, "MakePath requires an even number of arguments"); + Path64 result; + details::MakePathGeneric(list, N, result); + return result; + } + + template::value && + !std::is_same::value, bool + >::type = true> + inline PathD MakePathD(const std::vector& list) + { + const auto size = list.size() - list.size() % 2; + if (list.size() != size) + DoError(non_pair_error_i); // non-fatal without exception handling + PathD result; + details::MakePathGeneric(list, size, result); + return result; + } + + template::value && + !std::is_same::value, bool + >::type = true> + inline PathD MakePathD(const T(&list)[N]) + { + // Make the compiler error on unpaired value (i.e. no runtime effects). + static_assert(N % 2 == 0, "MakePath requires an even number of arguments"); + PathD result; + details::MakePathGeneric(list, N, result); + return result; + } + +#ifdef USINGZ + template + inline Path64 MakePathZ(const T2(&list)[N]) + { + static_assert(N % 3 == 0 && std::numeric_limits::is_integer, + "MakePathZ requires integer values in multiples of 3"); + std::size_t size = N / 3; + Path64 result(size); + for (size_t i = 0; i < size; ++i) + result[i] = Point64(list[i * 3], + list[i * 3 + 1], list[i * 3 + 2]); + return result; + } + + template + inline PathD MakePathZD(const T2(&list)[N]) + { + static_assert(N % 3 == 0, + "MakePathZD requires values in multiples of 3"); + std::size_t size = N / 3; + PathD result(size); + if constexpr (std::numeric_limits::is_integer) + for (size_t i = 0; i < size; ++i) + result[i] = PointD(list[i * 3], + list[i * 3 + 1], list[i * 3 + 2]); + else + for (size_t i = 0; i < size; ++i) + result[i] = PointD(list[i * 3], list[i * 3 + 1], + static_cast(list[i * 3 + 2])); + return result; + } +#endif + + inline Path64 TrimCollinear(const Path64& p, bool is_open_path = false) + { + size_t len = p.size(); + if (len < 3) + { + if (!is_open_path || len < 2 || p[0] == p[1]) return Path64(); + else return p; + } + + Path64 dst; + dst.reserve(len); + Path64::const_iterator srcIt = p.cbegin(), prevIt, stop = p.cend() - 1; + + if (!is_open_path) + { + while (srcIt != stop && IsCollinear(*stop, *srcIt, *(srcIt + 1))) + ++srcIt; + while (srcIt != stop && IsCollinear(*(stop - 1), *stop, *srcIt)) + --stop; + if (srcIt == stop) return Path64(); + } + + prevIt = srcIt++; + dst.emplace_back(*prevIt); + for (; srcIt != stop; ++srcIt) + { + if (!IsCollinear(*prevIt, *srcIt, *(srcIt + 1))) + { + prevIt = srcIt; + dst.emplace_back(*prevIt); + } + } + + if (is_open_path) + dst.emplace_back(*srcIt); + else if (!IsCollinear(*prevIt, *stop, dst[0])) + dst.emplace_back(*stop); + else + { + while (dst.size() > 2 && + IsCollinear(dst[dst.size() - 1], dst[dst.size() - 2], dst[0])) + dst.pop_back(); + if (dst.size() < 3) return Path64(); + } + return dst; + } + + inline PathD TrimCollinear(const PathD& path, int precision, bool is_open_path = false) + { + int error_code = 0; + CheckPrecisionRange(precision, error_code); + if (error_code) return PathD(); + const double scale = std::pow(10, precision); + Path64 p = ScalePath(path, scale, error_code); + if (error_code) return PathD(); + p = TrimCollinear(p, is_open_path); + return ScalePath(p, 1/scale, error_code); + } + + template + inline double Distance(const Point pt1, const Point pt2) + { + return std::sqrt(DistanceSqr(pt1, pt2)); + } + + template + inline double Length(const Path& path, bool is_closed_path = false) + { + double result = 0.0; + if (path.size() < 2) return result; + auto it = path.cbegin(), stop = path.end() - 1; + for (; it != stop; ++it) + result += Distance(*it, *(it + 1)); + if (is_closed_path) + result += Distance(*stop, *path.cbegin()); + return result; + } + + + template + inline bool NearCollinear(const Point& pt1, const Point& pt2, const Point& pt3, double sin_sqrd_min_angle_rads) + { + double cp = std::abs(CrossProduct(pt1, pt2, pt3)); + return (cp * cp) / (DistanceSqr(pt1, pt2) * DistanceSqr(pt2, pt3)) < sin_sqrd_min_angle_rads; + } + + template + inline Path Ellipse(const Rect& rect, size_t steps = 0) + { + return Ellipse(rect.MidPoint(), + static_cast(rect.Width()) *0.5, + static_cast(rect.Height()) * 0.5, steps); + } + + template + inline Path Ellipse(const Point& center, + double radiusX, double radiusY = 0, size_t steps = 0) + { + if (radiusX <= 0) return Path(); + if (radiusY <= 0) radiusY = radiusX; + if (steps <= 2) + steps = static_cast(PI * sqrt((radiusX + radiusY) / 2)); + + double si = std::sin(2 * PI / steps); + double co = std::cos(2 * PI / steps); + double dx = co, dy = si; + Path result; + result.reserve(steps); + result.emplace_back(center.x + radiusX, static_cast(center.y)); + for (size_t i = 1; i < steps; ++i) + { + result.emplace_back(center.x + radiusX * dx, center.y + radiusY * dy); + double x = dx * co - dy * si; + dy = dy * co + dx * si; + dx = x; + } + return result; + } + + inline size_t GetNext(size_t current, size_t high, + const std::vector& flags) + { + ++current; + while (current <= high && flags[current]) ++current; + if (current <= high) return current; + current = 0; + while (flags[current]) ++current; + return current; + } + + inline size_t GetPrior(size_t current, size_t high, + const std::vector& flags) + { + if (current == 0) current = high; + else --current; + while (current > 0 && flags[current]) --current; + if (!flags[current]) return current; + current = high; + while (flags[current]) --current; + return current; + } + + template + inline Path SimplifyPath(const Path &path, + double epsilon, bool isClosedPath = true) + { + const size_t len = path.size(), high = len -1; + const double epsSqr = Sqr(epsilon); + if (len < 4) return Path(path); + + std::vector flags(len); + std::vector distSqr(len); + size_t prior = high, curr = 0, start, next, prior2; + if (isClosedPath) + { + distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]); + distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]); + } + else + { + distSqr[0] = MAX_DBL; + distSqr[high] = MAX_DBL; + } + for (size_t i = 1; i < high; ++i) + distSqr[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]); + + for (;;) + { + if (distSqr[curr] > epsSqr) + { + start = curr; + do + { + curr = GetNext(curr, high, flags); + } while (curr != start && distSqr[curr] > epsSqr); + if (curr == start) break; + } + + prior = GetPrior(curr, high, flags); + next = GetNext(curr, high, flags); + if (next == prior) break; + + // flag for removal the smaller of adjacent 'distances' + if (distSqr[next] < distSqr[curr]) + { + prior2 = prior; + prior = curr; + curr = next; + next = GetNext(next, high, flags); + } + else + prior2 = GetPrior(prior, high, flags); + + flags[curr] = true; + curr = next; + next = GetNext(next, high, flags); + + if (isClosedPath || ((curr != high) && (curr != 0))) + distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); + if (isClosedPath || ((prior != 0) && (prior != high))) + distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]); + } + Path result; + result.reserve(len); + for (typename Path::size_type i = 0; i < len; ++i) + if (!flags[i]) result.emplace_back(path[i]); + return result; + } + + template + inline Paths SimplifyPaths(const Paths &paths, + double epsilon, bool isClosedPath = true) + { + Paths result; + result.reserve(paths.size()); + for (const auto& path : paths) + result.emplace_back(std::move(SimplifyPath(path, epsilon, isClosedPath))); + return result; + } + + template + inline void RDP(const Path path, std::size_t begin, + std::size_t end, double epsSqrd, std::vector& flags) + { + typename Path::size_type idx = 0; + double max_d = 0; + while (end > begin && path[begin] == path[end]) flags[end--] = false; + for (typename Path::size_type i = begin + 1; i < end; ++i) + { + // PerpendicDistFromLineSqrd - avoids expensive Sqrt() + double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]); + if (d <= max_d) continue; + max_d = d; + idx = i; + } + if (max_d <= epsSqrd) return; + flags[idx] = true; + if (idx > begin + 1) RDP(path, begin, idx, epsSqrd, flags); + if (idx < end - 1) RDP(path, idx, end, epsSqrd, flags); + } + + template + inline Path RamerDouglasPeucker(const Path& path, double epsilon) + { + const typename Path::size_type len = path.size(); + if (len < 5) return Path(path); + std::vector flags(len); + flags[0] = true; + flags[len - 1] = true; + RDP(path, 0, len - 1, Sqr(epsilon), flags); + Path result; + result.reserve(len); + for (typename Path::size_type i = 0; i < len; ++i) + if (flags[i]) + result.emplace_back(path[i]); + return result; + } + + template + inline Paths RamerDouglasPeucker(const Paths& paths, double epsilon) + { + Paths result; + result.reserve(paths.size()); + std::transform(paths.begin(), paths.end(), back_inserter(result), + [epsilon](const auto& path) + { return RamerDouglasPeucker(path, epsilon); }); + return result; + } + +} // end Clipper2Lib namespace + +#endif // CLIPPER_H diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h new file mode 100644 index 0000000000..60d8d17c33 --- /dev/null +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h @@ -0,0 +1,120 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 1 November 2023 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2023 * +* Purpose : Minkowski Sum and Difference * +* License : https://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#ifndef CLIPPER_MINKOWSKI_H +#define CLIPPER_MINKOWSKI_H + +#include "clipper2/clipper.core.h" + +#ifdef USINGZ +namespace Clipper2Lib_Z { +#else +namespace Clipper2Lib { +#endif + + namespace detail + { + inline Paths64 Minkowski(const Path64& pattern, const Path64& path, bool isSum, bool isClosed) + { + size_t delta = isClosed ? 0 : 1; + size_t patLen = pattern.size(), pathLen = path.size(); + if (patLen == 0 || pathLen == 0) return Paths64(); + Paths64 tmp; + tmp.reserve(pathLen); + + if (isSum) + { + for (const Point64& p : path) + { + Path64 path2(pattern.size()); + std::transform(pattern.cbegin(), pattern.cend(), + path2.begin(), [p](const Point64& pt2) {return p + pt2; }); + tmp.emplace_back(std::move(path2)); + } + } + else + { + for (const Point64& p : path) + { + Path64 path2(pattern.size()); + std::transform(pattern.cbegin(), pattern.cend(), + path2.begin(), [p](const Point64& pt2) {return p - pt2; }); + tmp.emplace_back(std::move(path2)); + } + } + + Paths64 result; + result.reserve((pathLen - delta) * patLen); + size_t g = isClosed ? pathLen - 1 : 0; + for (size_t h = patLen - 1, i = delta; i < pathLen; ++i) + { + for (size_t j = 0; j < patLen; j++) + { + Path64 quad; + quad.reserve(4); + { + quad.emplace_back(tmp[g][h]); + quad.emplace_back(tmp[i][h]); + quad.emplace_back(tmp[i][j]); + quad.emplace_back(tmp[g][j]); + }; + if (!IsPositive(quad)) + std::reverse(quad.begin(), quad.end()); + result.emplace_back(std::move(quad)); + h = j; + } + g = i; + } + return result; + } + + inline Paths64 Union(const Paths64& subjects, FillRule fillrule) + { + Paths64 result; + Clipper64 clipper; + clipper.AddSubject(subjects); + clipper.Execute(ClipType::Union, fillrule, result); + return result; + } + + } // namespace internal + + inline Paths64 MinkowskiSum(const Path64& pattern, const Path64& path, bool isClosed) + { + return detail::Union(detail::Minkowski(pattern, path, true, isClosed), FillRule::NonZero); + } + + inline PathsD MinkowskiSum(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2) + { + int error_code = 0; + double scale = pow(10, decimalPlaces); + Path64 pat64 = ScalePath(pattern, scale, error_code); + Path64 path64 = ScalePath(path, scale, error_code); + Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, true, isClosed), FillRule::NonZero); + return ScalePaths(tmp, 1 / scale, error_code); + } + + inline Paths64 MinkowskiDiff(const Path64& pattern, const Path64& path, bool isClosed) + { + return detail::Union(detail::Minkowski(pattern, path, false, isClosed), FillRule::NonZero); + } + + inline PathsD MinkowskiDiff(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2) + { + int error_code = 0; + double scale = pow(10, decimalPlaces); + Path64 pat64 = ScalePath(pattern, scale, error_code); + Path64 path64 = ScalePath(path, scale, error_code); + Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, false, isClosed), FillRule::NonZero); + return ScalePaths(tmp, 1 / scale, error_code); + } + +} // Clipper2Lib namespace + +#endif // CLIPPER_MINKOWSKI_H diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h new file mode 100644 index 0000000000..a7d53df36f --- /dev/null +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h @@ -0,0 +1,129 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 22 January 2025 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2025 * +* Purpose : Path Offset (Inflate/Shrink) * +* License : https://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#ifndef CLIPPER_OFFSET_H_ +#define CLIPPER_OFFSET_H_ + +#include "clipper.core.h" +#include "clipper.engine.h" +#include + +#ifdef USINGZ +namespace Clipper2Lib_Z { +#else +namespace Clipper2Lib { +#endif + +enum class JoinType { Square, Bevel, Round, Miter }; +//Square : Joins are 'squared' at exactly the offset distance (more complex code) +//Bevel : Similar to Square, but the offset distance varies with angle (simple code & faster) + +enum class EndType {Polygon, Joined, Butt, Square, Round}; +//Butt : offsets both sides of a path, with square blunt ends +//Square : offsets both sides of a path, with square extended ends +//Round : offsets both sides of a path, with round extended ends +//Joined : offsets both sides of a path, with joined ends +//Polygon: offsets only one side of a closed path + +typedef std::function DeltaCallback64; + +class ClipperOffset { +private: + + class Group { + public: + Paths64 paths_in; + std::optional lowest_path_idx{}; + bool is_reversed = false; + JoinType join_type; + EndType end_type; + Group(const Paths64& _paths, JoinType _join_type, EndType _end_type); + }; + + int error_code_ = 0; + double delta_ = 0.0; + double group_delta_ = 0.0; + double temp_lim_ = 0.0; + double steps_per_rad_ = 0.0; + double step_sin_ = 0.0; + double step_cos_ = 0.0; + PathD norms; + Path64 path_out; + Paths64* solution = nullptr; + PolyTree64* solution_tree = nullptr; + std::vector groups_; + JoinType join_type_ = JoinType::Bevel; + EndType end_type_ = EndType::Polygon; + + double miter_limit_ = 0.0; + double arc_tolerance_ = 0.0; + bool preserve_collinear_ = false; + bool reverse_solution_ = false; + +#ifdef USINGZ + ZCallback64 zCallback64_ = nullptr; + void ZCB(const Point64& bot1, const Point64& top1, + const Point64& bot2, const Point64& top2, Point64& ip); +#endif + DeltaCallback64 deltaCallback64_ = nullptr; + size_t CalcSolutionCapacity(); + bool CheckReverseOrientation(); + void DoBevel(const Path64& path, size_t j, size_t k); + void DoSquare(const Path64& path, size_t j, size_t k); + void DoMiter(const Path64& path, size_t j, size_t k, double cos_a); + void DoRound(const Path64& path, size_t j, size_t k, double angle); + void BuildNormals(const Path64& path); + void OffsetPolygon(Group& group, const Path64& path); + void OffsetOpenJoined(Group& group, const Path64& path); + void OffsetOpenPath(Group& group, const Path64& path); + void OffsetPoint(Group& group, const Path64& path, size_t j, size_t k); + void DoGroupOffset(Group &group); + void ExecuteInternal(double delta); +public: + explicit ClipperOffset(double miter_limit = 2.0, + double arc_tolerance = 0.0, + bool preserve_collinear = false, + bool reverse_solution = false) : + miter_limit_(miter_limit), arc_tolerance_(arc_tolerance), + preserve_collinear_(preserve_collinear), + reverse_solution_(reverse_solution) { }; + + ~ClipperOffset() { Clear(); }; + + int ErrorCode() const { return error_code_; }; + void AddPath(const Path64& path, JoinType jt_, EndType et_); + void AddPaths(const Paths64& paths, JoinType jt_, EndType et_); + void Clear() { groups_.clear(); norms.clear(); }; + + void Execute(double delta, Paths64& sols_64); + void Execute(double delta, PolyTree64& polytree); + void Execute(DeltaCallback64 delta_cb, Paths64& paths); + + double MiterLimit() const { return miter_limit_; } + void MiterLimit(double miter_limit) { miter_limit_ = miter_limit; } + + //ArcTolerance: needed for rounded offsets (See offset_triginometry2.svg) + double ArcTolerance() const { return arc_tolerance_; } + void ArcTolerance(double arc_tolerance) { arc_tolerance_ = arc_tolerance; } + + bool PreserveCollinear() const { return preserve_collinear_; } + void PreserveCollinear(bool preserve_collinear){preserve_collinear_ = preserve_collinear;} + + bool ReverseSolution() const { return reverse_solution_; } + void ReverseSolution(bool reverse_solution) {reverse_solution_ = reverse_solution;} + +#ifdef USINGZ + void SetZCallback(ZCallback64 cb) { zCallback64_ = cb; } +#endif + void SetDeltaCallback(DeltaCallback64 cb) { deltaCallback64_ = cb; } + +}; + +} +#endif /* CLIPPER_OFFSET_H_ */ diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h new file mode 100644 index 0000000000..0825a93d76 --- /dev/null +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h @@ -0,0 +1,83 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 5 July 2024 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2024 * +* Purpose : FAST rectangular clipping * +* License : https://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#ifndef CLIPPER_RECTCLIP_H +#define CLIPPER_RECTCLIP_H + +#include "clipper2/clipper.core.h" +#include + +#ifdef USINGZ +namespace Clipper2Lib_Z { +#else +namespace Clipper2Lib { +#endif + + // Location: the order is important here, see StartLocsIsClockwise() + enum class Location { Left, Top, Right, Bottom, Inside }; + + class OutPt2; + typedef std::vector OutPt2List; + + class OutPt2 { + public: + Point64 pt; + size_t owner_idx = 0; + OutPt2List* edge = nullptr; + OutPt2* next = nullptr; + OutPt2* prev = nullptr; + }; + + //------------------------------------------------------------------------------ + // RectClip64 + //------------------------------------------------------------------------------ + + class RectClip64 { + private: + void ExecuteInternal(const Path64& path); + Path64 GetPath(OutPt2*& op); + protected: + const Rect64 rect_; + const Path64 rect_as_path_; + const Point64 rect_mp_; + Rect64 path_bounds_; + std::deque op_container_; + OutPt2List results_; // each path can be broken into multiples + OutPt2List edges_[8]; // clockwise and counter-clockwise + std::vector start_locs_; + void CheckEdges(); + void TidyEdges(size_t idx, OutPt2List& cw, OutPt2List& ccw); + void GetNextLocation(const Path64& path, + Location& loc, size_t& i, size_t highI); + OutPt2* Add(Point64 pt, bool start_new = false); + void AddCorner(Location prev, Location curr); + void AddCorner(Location& loc, bool isClockwise); + public: + explicit RectClip64(const Rect64& rect) : + rect_(rect), + rect_as_path_(rect.AsPath()), + rect_mp_(rect.MidPoint()) {} + Paths64 Execute(const Paths64& paths); + }; + + //------------------------------------------------------------------------------ + // RectClipLines64 + //------------------------------------------------------------------------------ + + class RectClipLines64 : public RectClip64 { + private: + void ExecuteInternal(const Path64& path); + Path64 GetPath(OutPt2*& op); + public: + explicit RectClipLines64(const Rect64& rect) : RectClip64(rect) {}; + Paths64 Execute(const Paths64& paths); + }; + +} // Clipper2Lib namespace +#endif // CLIPPER_RECTCLIP_H diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper.version.h b/src/clipper2/Clipper2Lib/include/clipper2/clipper.version.h new file mode 100644 index 0000000000..661b0f1c58 --- /dev/null +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper.version.h @@ -0,0 +1,6 @@ +#ifndef CLIPPER_VERSION_H +#define CLIPPER_VERSION_H + +constexpr auto CLIPPER2_VERSION = "1.5.2"; + +#endif // CLIPPER_VERSION_H diff --git a/src/clipper2/Clipper2Lib/include/clipper2/clipper2_z.hpp b/src/clipper2/Clipper2Lib/include/clipper2/clipper2_z.hpp new file mode 100644 index 0000000000..ab20510b6a --- /dev/null +++ b/src/clipper2/Clipper2Lib/include/clipper2/clipper2_z.hpp @@ -0,0 +1,17 @@ +// Hackish wrapper around the ClipperLib library to compile the Clipper2 library with the Z support. + +#ifndef clipper2_z_hpp +#ifdef CLIPPER_H +#error "You should include clipper2_z.hpp before clipper.h" +#endif + +#define clipper2_z_hpp + +// Enable the Z coordinate support. +#define USINGZ + +#include "clipper.h" + +#undef CLIPPER_H +#undef USINGZ +#endif // clipper2_z_hpp diff --git a/src/clipper2/Clipper2Lib/src/clipper.engine.cpp b/src/clipper2/Clipper2Lib/src/clipper.engine.cpp new file mode 100644 index 0000000000..fd77bda7bb --- /dev/null +++ b/src/clipper2/Clipper2Lib/src/clipper.engine.cpp @@ -0,0 +1,3151 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 17 September 2024 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2024 * +* Purpose : This is the main polygon clipping module * +* License : https://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#include "clipper2/clipper.engine.h" +#include "clipper2/clipper.h" +#include + +// https://github.com/AngusJohnson/Clipper2/discussions/334 +// #discussioncomment-4248602 +#if defined(_MSC_VER) && ( defined(_M_AMD64) || defined(_M_X64) ) +#include +#include +#define fmin(a,b) _mm_cvtsd_f64(_mm_min_sd(_mm_set_sd(a),_mm_set_sd(b))) +#define fmax(a,b) _mm_cvtsd_f64(_mm_max_sd(_mm_set_sd(a),_mm_set_sd(b))) +#define nearbyint(a) _mm_cvtsd_si64(_mm_set_sd(a)) /* Note: expression type is (int64_t) */ +#endif + +#ifdef USINGZ +namespace Clipper2Lib_Z { +#else +namespace Clipper2Lib { +#endif + + static const Rect64 invalid_rect = Rect64(false); + + // Every closed path (ie polygon) is made up of a series of vertices forming edge + // 'bounds' that alternate between ascending bounds (containing edges going up + // relative to the Y-axis) and descending bounds. 'Local Minima' refers to + // vertices where ascending and descending bounds join at the bottom, and + // 'Local Maxima' are where ascending and descending bounds join at the top. + + struct Scanline { + int64_t y = 0; + Scanline* next = nullptr; + + explicit Scanline(int64_t y_) : y(y_) {} + }; + + struct HorzSegSorter { + inline bool operator()(const HorzSegment& hs1, const HorzSegment& hs2) + { + if (!hs1.right_op || !hs2.right_op) return (hs1.right_op); + return hs2.left_op->pt.x > hs1.left_op->pt.x; + } + }; + + struct LocMinSorter { + inline bool operator()(const LocalMinima_ptr& locMin1, + const LocalMinima_ptr& locMin2) + { + if (locMin2->vertex->pt.y != locMin1->vertex->pt.y) + return locMin2->vertex->pt.y < locMin1->vertex->pt.y; + else + return locMin2->vertex->pt.x > locMin1->vertex->pt.x; + } + }; + + + inline bool IsOdd(int val) + { + return (val & 1) ? true : false; + } + + + inline bool IsHotEdge(const Active& e) + { + return (e.outrec); + } + + + inline bool IsOpen(const Active& e) + { + return (e.local_min->is_open); + } + + + inline bool IsOpenEnd(const Vertex& v) + { + return (v.flags & (VertexFlags::OpenStart | VertexFlags::OpenEnd)) != + VertexFlags::Empty; + } + + + inline bool IsOpenEnd(const Active& ae) + { + return IsOpenEnd(*ae.vertex_top); + } + + + inline Active* GetPrevHotEdge(const Active& e) + { + Active* prev = e.prev_in_ael; + while (prev && (IsOpen(*prev) || !IsHotEdge(*prev))) + prev = prev->prev_in_ael; + return prev; + } + + inline bool IsFront(const Active& e) + { + return (&e == e.outrec->front_edge); + } + + inline bool IsInvalidPath(OutPt* op) + { + return (!op || op->next == op); + } + + /******************************************************************************* + * Dx: 0(90deg) * + * | * + * +inf (180deg) <--- o ---> -inf (0deg) * + *******************************************************************************/ + + inline double GetDx(const Point64& pt1, const Point64& pt2) + { + double dy = double(pt2.y - pt1.y); + if (dy != 0) + return double(pt2.x - pt1.x) / dy; + else if (pt2.x > pt1.x) + return -std::numeric_limits::max(); + else + return std::numeric_limits::max(); + } + + inline int64_t TopX(const Active& ae, const int64_t currentY) + { + if ((currentY == ae.top.y) || (ae.top.x == ae.bot.x)) return ae.top.x; + else if (currentY == ae.bot.y) return ae.bot.x; + else return ae.bot.x + static_cast(nearbyint(ae.dx * (currentY - ae.bot.y))); + // nb: std::nearbyint (or std::round) substantially *improves* performance here + // as it greatly improves the likelihood of edge adjacency in ProcessIntersectList(). + } + + + inline bool IsHorizontal(const Active& e) + { + return (e.top.y == e.bot.y); + } + + + inline bool IsHeadingRightHorz(const Active& e) + { + return e.dx == -std::numeric_limits::max(); + } + + + inline bool IsHeadingLeftHorz(const Active& e) + { + return e.dx == std::numeric_limits::max(); + } + + + inline void SwapActives(Active*& e1, Active*& e2) + { + Active* e = e1; + e1 = e2; + e2 = e; + } + + inline PathType GetPolyType(const Active& e) + { + return e.local_min->polytype; + } + + inline bool IsSamePolyType(const Active& e1, const Active& e2) + { + return e1.local_min->polytype == e2.local_min->polytype; + } + + inline void SetDx(Active& e) + { + e.dx = GetDx(e.bot, e.top); + } + + inline Vertex* NextVertex(const Active& e) + { + if (e.wind_dx > 0) + return e.vertex_top->next; + else + return e.vertex_top->prev; + } + + //PrevPrevVertex: useful to get the (inverted Y-axis) top of the + //alternate edge (ie left or right bound) during edge insertion. + inline Vertex* PrevPrevVertex(const Active& ae) + { + if (ae.wind_dx > 0) + return ae.vertex_top->prev->prev; + else + return ae.vertex_top->next->next; + } + + + inline Active* ExtractFromSEL(Active* ae) + { + Active* res = ae->next_in_sel; + if (res) + res->prev_in_sel = ae->prev_in_sel; + ae->prev_in_sel->next_in_sel = res; + return res; + } + + + inline void Insert1Before2InSEL(Active* ae1, Active* ae2) + { + ae1->prev_in_sel = ae2->prev_in_sel; + if (ae1->prev_in_sel) + ae1->prev_in_sel->next_in_sel = ae1; + ae1->next_in_sel = ae2; + ae2->prev_in_sel = ae1; + } + + inline bool IsMaxima(const Vertex& v) + { + return ((v.flags & VertexFlags::LocalMax) != VertexFlags::Empty); + } + + + inline bool IsMaxima(const Active& e) + { + return IsMaxima(*e.vertex_top); + } + + inline Vertex* GetCurrYMaximaVertex_Open(const Active& e) + { + Vertex* result = e.vertex_top; + if (e.wind_dx > 0) + while ((result->next->pt.y == result->pt.y) && + ((result->flags & (VertexFlags::OpenEnd | + VertexFlags::LocalMax)) == VertexFlags::Empty)) + result = result->next; + else + while (result->prev->pt.y == result->pt.y && + ((result->flags & (VertexFlags::OpenEnd | + VertexFlags::LocalMax)) == VertexFlags::Empty)) + result = result->prev; + if (!IsMaxima(*result)) result = nullptr; // not a maxima + return result; + } + + inline Vertex* GetCurrYMaximaVertex(const Active& e) + { + Vertex* result = e.vertex_top; + if (e.wind_dx > 0) + while (result->next->pt.y == result->pt.y) result = result->next; + else + while (result->prev->pt.y == result->pt.y) result = result->prev; + if (!IsMaxima(*result)) result = nullptr; // not a maxima + return result; + } + + Active* GetMaximaPair(const Active& e) + { + Active* e2; + e2 = e.next_in_ael; + while (e2) + { + if (e2->vertex_top == e.vertex_top) return e2; // Found! + e2 = e2->next_in_ael; + } + return nullptr; + } + + inline int PointCount(OutPt* op) + { + OutPt* op2 = op; + int cnt = 0; + do + { + op2 = op2->next; + ++cnt; + } while (op2 != op); + return cnt; + } + + inline OutPt* DuplicateOp(OutPt* op, bool insert_after) + { + OutPt* result = new OutPt(op->pt, op->outrec); + if (insert_after) + { + result->next = op->next; + result->next->prev = result; + result->prev = op; + op->next = result; + } + else + { + result->prev = op->prev; + result->prev->next = result; + result->next = op; + op->prev = result; + } + return result; + } + + inline OutPt* DisposeOutPt(OutPt* op) + { + OutPt* result = op->next; + op->prev->next = op->next; + op->next->prev = op->prev; + delete op; + return result; + } + + + inline void DisposeOutPts(OutRec* outrec) + { + OutPt* op = outrec->pts; + op->prev->next = nullptr; + while (op) + { + OutPt* tmp = op; + op = op->next; + delete tmp; + }; + outrec->pts = nullptr; + } + + + bool IntersectListSort(const IntersectNode& a, const IntersectNode& b) + { + //note different inequality tests ... + return (a.pt.y == b.pt.y) ? (a.pt.x < b.pt.x) : (a.pt.y > b.pt.y); + } + + + inline void SetSides(OutRec& outrec, Active& start_edge, Active& end_edge) + { + outrec.front_edge = &start_edge; + outrec.back_edge = &end_edge; + } + + + void SwapOutrecs(Active& e1, Active& e2) + { + OutRec* or1 = e1.outrec; + OutRec* or2 = e2.outrec; + if (or1 == or2) + { + Active* e = or1->front_edge; + or1->front_edge = or1->back_edge; + or1->back_edge = e; + return; + } + if (or1) + { + if (&e1 == or1->front_edge) + or1->front_edge = &e2; + else + or1->back_edge = &e2; + } + if (or2) + { + if (&e2 == or2->front_edge) + or2->front_edge = &e1; + else + or2->back_edge = &e1; + } + e1.outrec = or2; + e2.outrec = or1; + } + + + double Area(OutPt* op) + { + //https://en.wikipedia.org/wiki/Shoelace_formula + double result = 0.0; + OutPt* op2 = op; + do + { + result += static_cast(op2->prev->pt.y + op2->pt.y) * + static_cast(op2->prev->pt.x - op2->pt.x); + op2 = op2->next; + } while (op2 != op); + return result * 0.5; + } + + inline double AreaTriangle(const Point64& pt1, + const Point64& pt2, const Point64& pt3) + { + return (static_cast(pt3.y + pt1.y) * static_cast(pt3.x - pt1.x) + + static_cast(pt1.y + pt2.y) * static_cast(pt1.x - pt2.x) + + static_cast(pt2.y + pt3.y) * static_cast(pt2.x - pt3.x)); + } + + void ReverseOutPts(OutPt* op) + { + if (!op) return; + + OutPt* op1 = op; + OutPt* op2; + + do + { + op2 = op1->next; + op1->next = op1->prev; + op1->prev = op2; + op1 = op2; + } while (op1 != op); + } + + inline void SwapSides(OutRec& outrec) + { + Active* e2 = outrec.front_edge; + outrec.front_edge = outrec.back_edge; + outrec.back_edge = e2; + outrec.pts = outrec.pts->next; + } + + inline OutRec* GetRealOutRec(OutRec* outrec) + { + while (outrec && !outrec->pts) outrec = outrec->owner; + return outrec; + } + + inline bool IsValidOwner(OutRec* outrec, OutRec* testOwner) + { + // prevent outrec owning itself either directly or indirectly + while (testOwner && testOwner != outrec) testOwner = testOwner->owner; + return !testOwner; + } + + inline void UncoupleOutRec(Active ae) + { + OutRec* outrec = ae.outrec; + if (!outrec) return; + outrec->front_edge->outrec = nullptr; + outrec->back_edge->outrec = nullptr; + outrec->front_edge = nullptr; + outrec->back_edge = nullptr; + } + + + inline bool PtsReallyClose(const Point64& pt1, const Point64& pt2) + { + return (std::llabs(pt1.x - pt2.x) < 2) && (std::llabs(pt1.y - pt2.y) < 2); + } + + inline bool IsVerySmallTriangle(const OutPt& op) + { + return op.next->next == op.prev && + (PtsReallyClose(op.prev->pt, op.next->pt) || + PtsReallyClose(op.pt, op.next->pt) || + PtsReallyClose(op.pt, op.prev->pt)); + } + + inline bool IsValidClosedPath(const OutPt* op) + { + return op && (op->next != op) && (op->next != op->prev) && + !IsVerySmallTriangle(*op); + } + + inline bool OutrecIsAscending(const Active* hotEdge) + { + return (hotEdge == hotEdge->outrec->front_edge); + } + + inline void SwapFrontBackSides(OutRec& outrec) + { + Active* tmp = outrec.front_edge; + outrec.front_edge = outrec.back_edge; + outrec.back_edge = tmp; + outrec.pts = outrec.pts->next; + } + + inline bool EdgesAdjacentInAEL(const IntersectNode& inode) + { + return (inode.edge1->next_in_ael == inode.edge2) || (inode.edge1->prev_in_ael == inode.edge2); + } + + inline bool IsJoined(const Active& e) + { + return e.join_with != JoinWith::NoJoin; + } + + inline void SetOwner(OutRec* outrec, OutRec* new_owner) + { + //precondition1: new_owner is never null + while (new_owner->owner && !new_owner->owner->pts) + new_owner->owner = new_owner->owner->owner; + OutRec* tmp = new_owner; + while (tmp && tmp != outrec) tmp = tmp->owner; + if (tmp) new_owner->owner = outrec->owner; + outrec->owner = new_owner; + } + + static PointInPolygonResult PointInOpPolygon(const Point64& pt, OutPt* op) + { + if (op == op->next || op->prev == op->next) + return PointInPolygonResult::IsOutside; + + OutPt* op2 = op; + do + { + if (op->pt.y != pt.y) break; + op = op->next; + } while (op != op2); + if (op->pt.y == pt.y) // not a proper polygon + return PointInPolygonResult::IsOutside; + + bool is_above = op->pt.y < pt.y, starting_above = is_above; + int val = 0; + op2 = op->next; + while (op2 != op) + { + if (is_above) + while (op2 != op && op2->pt.y < pt.y) op2 = op2->next; + else + while (op2 != op && op2->pt.y > pt.y) op2 = op2->next; + if (op2 == op) break; + + // must have touched or crossed the pt.Y horizontal + // and this must happen an even number of times + + if (op2->pt.y == pt.y) // touching the horizontal + { + if (op2->pt.x == pt.x || (op2->pt.y == op2->prev->pt.y && + (pt.x < op2->prev->pt.x) != (pt.x < op2->pt.x))) + return PointInPolygonResult::IsOn; + + op2 = op2->next; + if (op2 == op) break; + continue; + } + + if (pt.x < op2->pt.x && pt.x < op2->prev->pt.x); + // do nothing because + // we're only interested in edges crossing on the left + else if ((pt.x > op2->prev->pt.x && pt.x > op2->pt.x)) + val = 1 - val; // toggle val + else + { + double d = CrossProduct(op2->prev->pt, op2->pt, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + is_above = !is_above; + op2 = op2->next; + } + + if (is_above != starting_above) + { + double d = CrossProduct(op2->prev->pt, op2->pt, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + + if (val == 0) return PointInPolygonResult::IsOutside; + else return PointInPolygonResult::IsInside; + } + + inline Path64 GetCleanPath(OutPt* op) + { + Path64 result; + OutPt* op2 = op; + while (op2->next != op && + ((op2->pt.x == op2->next->pt.x && op2->pt.x == op2->prev->pt.x) || + (op2->pt.y == op2->next->pt.y && op2->pt.y == op2->prev->pt.y))) op2 = op2->next; + result.emplace_back(op2->pt); + OutPt* prevOp = op2; + op2 = op2->next; + while (op2 != op) + { + if ((op2->pt.x != op2->next->pt.x || op2->pt.x != prevOp->pt.x) && + (op2->pt.y != op2->next->pt.y || op2->pt.y != prevOp->pt.y)) + { + result.emplace_back(op2->pt); + prevOp = op2; + } + op2 = op2->next; + } + return result; + } + + inline bool Path1InsidePath2(OutPt* op1, OutPt* op2) + { + // we need to make some accommodation for rounding errors + // so we won't jump if the first vertex is found outside + PointInPolygonResult result; + int outside_cnt = 0; + OutPt* op = op1; + do + { + result = PointInOpPolygon(op->pt, op2); + if (result == PointInPolygonResult::IsOutside) ++outside_cnt; + else if (result == PointInPolygonResult::IsInside) --outside_cnt; + op = op->next; + } while (op != op1 && std::abs(outside_cnt) < 2); + if (std::abs(outside_cnt) > 1) return (outside_cnt < 0); + // since path1's location is still equivocal, check its midpoint + Point64 mp = GetBounds(GetCleanPath(op1)).MidPoint(); + Path64 path2 = GetCleanPath(op2); + return PointInPolygon(mp, path2) != PointInPolygonResult::IsOutside; + } + + //------------------------------------------------------------------------------ + //------------------------------------------------------------------------------ + + void AddLocMin(LocalMinimaList& list, + Vertex& vert, PathType polytype, bool is_open) + { + //make sure the vertex is added only once ... + if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::Empty) return; + + vert.flags = (vert.flags | VertexFlags::LocalMin); + list.emplace_back(std::make_unique (&vert, polytype, is_open)); + } + + void AddPaths_(const Paths64& paths, PathType polytype, bool is_open, + std::vector& vertexLists, LocalMinimaList& locMinList) + { + const auto total_vertex_count = + std::accumulate(paths.begin(), paths.end(), size_t(0), + [](const auto& a, const Path64& path) + {return a + 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; + + if (path.empty()) + continue; + + 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::Empty; + 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(locMinList , *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(locMinList, *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(locMinList, *prev_v, polytype, is_open); + } + else if (going_up != going_up0) + { + if (going_up0) AddLocMin(locMinList, *prev_v, polytype, false); + else prev_v->flags = prev_v->flags | VertexFlags::LocalMax; + } + } // end processing current path + + vertexLists.emplace_back(vertices); + } + + //------------------------------------------------------------------------------ + // ReuseableDataContainer64 methods ... + //------------------------------------------------------------------------------ + + void ReuseableDataContainer64::AddLocMin(Vertex& vert, PathType polytype, bool is_open) + { + //make sure the vertex is added only once ... + if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::Empty) return; + + vert.flags = (vert.flags | VertexFlags::LocalMin); + minima_list_.emplace_back(std::make_unique (&vert, polytype, is_open)); + } + + void ReuseableDataContainer64::AddPaths(const Paths64& paths, + PathType polytype, bool is_open) + { + AddPaths_(paths, polytype, is_open, vertex_lists_, minima_list_); + } + + ReuseableDataContainer64::~ReuseableDataContainer64() + { + Clear(); + } + + void ReuseableDataContainer64::Clear() + { + minima_list_.clear(); + for (auto v : vertex_lists_) delete[] v; + vertex_lists_.clear(); + } + + //------------------------------------------------------------------------------ + // ClipperBase methods ... + //------------------------------------------------------------------------------ + + ClipperBase::~ClipperBase() + { + Clear(); + } + + void ClipperBase::DeleteEdges(Active*& e) + { + while (e) + { + Active* e2 = e; + e = e->next_in_ael; + delete e2; + } + } + + void ClipperBase::CleanUp() + { + DeleteEdges(actives_); + scanline_list_ = std::priority_queue(); + intersect_nodes_.clear(); + DisposeAllOutRecs(); + horz_seg_list_.clear(); + horz_join_list_.clear(); + } + + + void ClipperBase::Clear() + { + CleanUp(); + DisposeVerticesAndLocalMinima(); + current_locmin_iter_ = minima_list_.begin(); + minima_list_sorted_ = false; + has_open_paths_ = false; + } + + + void ClipperBase::Reset() + { + if (!minima_list_sorted_) + { + std::stable_sort(minima_list_.begin(), minima_list_.end(), LocMinSorter()); //#594 + minima_list_sorted_ = true; + } + LocalMinimaList::const_reverse_iterator i; + for (i = minima_list_.rbegin(); i != minima_list_.rend(); ++i) + InsertScanline((*i)->vertex->pt.y); + + current_locmin_iter_ = minima_list_.begin(); + actives_ = nullptr; + sel_ = nullptr; + succeeded_ = true; + } + + +#ifdef USINGZ + void ClipperBase::SetZ(const Active& e1, const Active& e2, Point64& ip) + { + if (!zCallback_) return; + // prioritize subject over clip vertices by passing + // subject vertices before clip vertices in the callback + if (GetPolyType(e1) == PathType::Subject) + { + if (ip == e1.bot) ip.z = e1.bot.z; + else if (ip == e1.top) ip.z = e1.top.z; + else if (ip == e2.bot) ip.z = e2.bot.z; + else if (ip == e2.top) ip.z = e2.top.z; + else ip.z = DefaultZ; + zCallback_(e1.bot, e1.top, e2.bot, e2.top, ip); + } + else + { + if (ip == e2.bot) ip.z = e2.bot.z; + else if (ip == e2.top) ip.z = e2.top.z; + else if (ip == e1.bot) ip.z = e1.bot.z; + else if (ip == e1.top) ip.z = e1.top.z; + else ip.z = DefaultZ; + zCallback_(e2.bot, e2.top, e1.bot, e1.top, ip); + } + } +#endif + + void ClipperBase::AddPath(const Path64& path, PathType polytype, bool is_open) + { + AddPaths(Paths64(1, path), 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; + AddPaths_(paths, polytype, is_open, vertex_lists_, minima_list_); + } + + void ClipperBase::AddReuseableData(const ReuseableDataContainer64& reuseable_data) + { + // nb: reuseable_data will continue to own the vertices + // and remains responsible for their clean up. + succeeded_ = false; + minima_list_sorted_ = false; + LocalMinimaList::const_iterator i; + for (i = reuseable_data.minima_list_.cbegin(); i != reuseable_data.minima_list_.cend(); ++i) + { + minima_list_.emplace_back(std::make_unique ((*i)->vertex, (*i)->polytype, (*i)->is_open)); + if ((*i)->is_open) has_open_paths_ = true; + } + } + + void ClipperBase::InsertScanline(int64_t y) + { + scanline_list_.push(y); + } + + + bool ClipperBase::PopScanline(int64_t& y) + { + if (scanline_list_.empty()) return false; + y = scanline_list_.top(); + scanline_list_.pop(); + while (!scanline_list_.empty() && y == scanline_list_.top()) + scanline_list_.pop(); // Pop duplicates. + return true; + } + + + bool ClipperBase::PopLocalMinima(int64_t y, LocalMinima*& local_minima) + { + if (current_locmin_iter_ == minima_list_.end() || (*current_locmin_iter_)->vertex->pt.y != y) return false; + local_minima = (current_locmin_iter_++)->get(); + return true; + } + + void ClipperBase::DisposeAllOutRecs() + { + for (auto outrec : outrec_list_) + { + if (outrec->pts) DisposeOutPts(outrec); + delete outrec; + } + outrec_list_.resize(0); + } + + void ClipperBase::DisposeVerticesAndLocalMinima() + { + minima_list_.clear(); + for (auto v : vertex_lists_) delete[] v; + vertex_lists_.clear(); + } + + + void ClipperBase::AddLocMin(Vertex& vert, PathType polytype, bool is_open) + { + //make sure the vertex is added only once ... + if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::Empty) return; + + vert.flags = (vert.flags | VertexFlags::LocalMin); + minima_list_.emplace_back(std::make_unique (&vert, polytype, is_open)); + } + + bool ClipperBase::IsContributingClosed(const Active& e) const + { + switch (fillrule_) + { + case FillRule::EvenOdd: + break; + case FillRule::NonZero: + if (abs(e.wind_cnt) != 1) return false; + break; + case FillRule::Positive: + if (e.wind_cnt != 1) return false; + break; + case FillRule::Negative: + if (e.wind_cnt != -1) return false; + break; + // Should never happen, but adding this to stop a compiler warning + default: + break; + } + + switch (cliptype_) + { + case ClipType::NoClip: + 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; + // Should never happen, but adding this to stop a compiler warning + default: + 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 (IsCollinear(PrevPrevVertex(resident)->pt, + resident.bot, resident.top)) return true; + else + //compare turning direction of the alternate bound + return (CrossProduct(PrevPrevVertex(resident)->pt, + newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft; + } + + + void ClipperBase::InsertLeftEdge(Active& e) + { + Active* e2; + if (!actives_) + { + e.prev_in_ael = nullptr; + e.next_in_ael = nullptr; + actives_ = &e; + } + else if (!IsValidAelOrder(*actives_, e)) + { + e.prev_in_ael = nullptr; + e.next_in_ael = actives_; + actives_->prev_in_ael = &e; + actives_ = &e; + } + else + { + e2 = actives_; + while (e2->next_in_ael && IsValidAelOrder(*e2->next_in_ael, e)) + e2 = e2->next_in_ael; + if (e2->join_with == JoinWith::Right) + e2 = e2->next_in_ael; + if (!e2) return; // should never happen and stops compiler warning :) + e.next_in_ael = e2->next_in_ael; + if (e2->next_in_ael) e2->next_in_ael->prev_in_ael = &e; + e.prev_in_ael = e2; + e2->next_in_ael = &e; + } + } + + + void InsertRightEdge(Active& e, Active& e2) + { + e2.next_in_ael = e.next_in_ael; + if (e.next_in_ael) e.next_in_ael->prev_in_ael = &e2; + e2.prev_in_ael = &e; + e.next_in_ael = &e2; + } + + + void ClipperBase::InsertLocalMinimaIntoAEL(int64_t bot_y) + { + LocalMinima* local_minima; + Active* left_bound, * right_bound; + //Add any local minima (if any) at BotY ... + //nb: horizontal local minima edges should contain locMin.vertex.prev + + while (PopLocalMinima(bot_y, local_minima)) + { + if ((local_minima->vertex->flags & VertexFlags::OpenStart) != VertexFlags::Empty) + { + left_bound = nullptr; + } + else + { + left_bound = new Active(); + left_bound->bot = local_minima->vertex->pt; + left_bound->curr_x = left_bound->bot.x; + left_bound->wind_dx = -1; + left_bound->vertex_top = local_minima->vertex->prev; // ie descending + left_bound->top = left_bound->vertex_top->pt; + left_bound->local_min = local_minima; + SetDx(*left_bound); + } + + if ((local_minima->vertex->flags & VertexFlags::OpenEnd) != VertexFlags::Empty) + { + right_bound = nullptr; + } + else + { + right_bound = new Active(); + right_bound->bot = local_minima->vertex->pt; + right_bound->curr_x = right_bound->bot.x; + right_bound->wind_dx = 1; + right_bound->vertex_top = local_minima->vertex->next; // ie ascending + right_bound->top = right_bound->vertex_top->pt; + right_bound->local_min = local_minima; + SetDx(*right_bound); + } + + //Currently LeftB is just the descending bound and RightB is the ascending. + //Now if the LeftB isn't on the left of RightB then we need swap them. + if (left_bound && right_bound) + { + if (IsHorizontal(*left_bound)) + { + if (IsHeadingRightHorz(*left_bound)) SwapActives(left_bound, right_bound); + } + else if (IsHorizontal(*right_bound)) + { + if (IsHeadingLeftHorz(*right_bound)) SwapActives(left_bound, right_bound); + } + else if (left_bound->dx < right_bound->dx) + SwapActives(left_bound, right_bound); + } + else if (!left_bound) + { + left_bound = right_bound; + right_bound = nullptr; + } + + bool contributing; + left_bound->is_left_bound = true; + InsertLeftEdge(*left_bound); + + if (IsOpen(*left_bound)) + { + SetWindCountForOpenPathEdge(*left_bound); + contributing = IsContributingOpen(*left_bound); + } + else + { + SetWindCountForClosedPathEdge(*left_bound); + contributing = IsContributingClosed(*left_bound); + } + + if (right_bound) + { + right_bound->is_left_bound = false; + right_bound->wind_cnt = left_bound->wind_cnt; + right_bound->wind_cnt2 = left_bound->wind_cnt2; + InsertRightEdge(*left_bound, *right_bound); /////// + if (contributing) + { + AddLocalMinPoly(*left_bound, *right_bound, left_bound->bot, true); + if (!IsHorizontal(*left_bound)) + CheckJoinLeft(*left_bound, left_bound->bot); + } + + while (right_bound->next_in_ael && + IsValidAelOrder(*right_bound->next_in_ael, *right_bound)) + { + IntersectEdges(*right_bound, *right_bound->next_in_ael, right_bound->bot); + SwapPositionsInAEL(*right_bound, *right_bound->next_in_ael); + } + + if (IsHorizontal(*right_bound)) + PushHorz(*right_bound); + else + { + CheckJoinRight(*right_bound, right_bound->bot); + InsertScanline(right_bound->top.y); + } + } + else if (contributing) + { + StartOpenPath(*left_bound, left_bound->bot); + } + + if (IsHorizontal(*left_bound)) + PushHorz(*left_bound); + else + InsertScanline(left_bound->top.y); + } // while (PopLocalMinima()) + } + + + inline void ClipperBase::PushHorz(Active& e) + { + e.next_in_sel = (sel_ ? sel_ : nullptr); + sel_ = &e; + } + + + inline bool ClipperBase::PopHorz(Active*& e) + { + e = sel_; + if (!e) return false; + sel_ = sel_->next_in_sel; + return true; + } + + + OutPt* ClipperBase::AddLocalMinPoly(Active& e1, Active& e2, + const Point64& pt, bool is_new) + { + OutRec* outrec = NewOutRec(); + e1.outrec = outrec; + e2.outrec = outrec; + + if (IsOpen(e1)) + { + outrec->owner = nullptr; + outrec->is_open = true; + if (e1.wind_dx > 0) + SetSides(*outrec, e1, e2); + else + SetSides(*outrec, e2, e1); + } + else + { + Active* prevHotEdge = GetPrevHotEdge(e1); + //e.windDx is the winding direction of the **input** paths + //and unrelated to the winding direction of output polygons. + //Output orientation is determined by e.outrec.frontE which is + //the ascending edge (see AddLocalMinPoly). + if (prevHotEdge) + { + if (using_polytree_) + SetOwner(outrec, prevHotEdge->outrec); + if (OutrecIsAscending(prevHotEdge) == is_new) + SetSides(*outrec, e2, e1); + else + SetSides(*outrec, e1, e2); + } + else + { + outrec->owner = nullptr; + if (is_new) + SetSides(*outrec, e1, e2); + else + SetSides(*outrec, e2, e1); + } + } + + OutPt* op = new OutPt(pt, outrec); + outrec->pts = op; + return op; + } + + + OutPt* ClipperBase::AddLocalMaxPoly(Active& e1, Active& e2, const Point64& pt) + { + if (IsJoined(e1)) Split(e1, pt); + if (IsJoined(e2)) Split(e2, pt); + + if (IsFront(e1) == IsFront(e2)) + { + if (IsOpenEnd(e1)) + SwapFrontBackSides(*e1.outrec); + else if (IsOpenEnd(e2)) + SwapFrontBackSides(*e2.outrec); + else + { + succeeded_ = false; + return nullptr; + } + } + + OutPt* result = AddOutPt(e1, pt); + if (e1.outrec == e2.outrec) + { + OutRec& outrec = *e1.outrec; + outrec.pts = result; + + if (using_polytree_) + { + Active* e = GetPrevHotEdge(e1); + if (!e) + outrec.owner = nullptr; + else + SetOwner(&outrec, e->outrec); + // nb: outRec.owner here is likely NOT the real + // owner but this will be checked in RecursiveCheckOwners() + } + + UncoupleOutRec(e1); + result = outrec.pts; + if (outrec.owner && !outrec.owner->front_edge) + outrec.owner = GetRealOutRec(outrec.owner); + } + //and to preserve the winding orientation of outrec ... + else if (IsOpen(e1)) + { + if (e1.wind_dx < 0) + JoinOutrecPaths(e1, e2); + else + JoinOutrecPaths(e2, e1); + } + else if (e1.outrec->idx < e2.outrec->idx) + JoinOutrecPaths(e1, e2); + else + JoinOutrecPaths(e2, e1); + return result; + } + + void ClipperBase::JoinOutrecPaths(Active& e1, Active& e2) + { + //join e2 outrec path onto e1 outrec path and then delete e2 outrec path + //pointers. (NB Only very rarely do the joining ends share the same coords.) + OutPt* p1_st = e1.outrec->pts; + OutPt* p2_st = e2.outrec->pts; + OutPt* p1_end = p1_st->next; + OutPt* p2_end = p2_st->next; + if (IsFront(e1)) + { + p2_end->prev = p1_st; + p1_st->next = p2_end; + p2_st->next = p1_end; + p1_end->prev = p2_st; + e1.outrec->pts = p2_st; + e1.outrec->front_edge = e2.outrec->front_edge; + if (e1.outrec->front_edge) + e1.outrec->front_edge->outrec = e1.outrec; + } + else + { + p1_end->prev = p2_st; + p2_st->next = p1_end; + p1_st->next = p2_end; + p2_end->prev = p1_st; + e1.outrec->back_edge = e2.outrec->back_edge; + if (e1.outrec->back_edge) + e1.outrec->back_edge->outrec = e1.outrec; + } + + //after joining, the e2.OutRec must contains no vertices ... + e2.outrec->front_edge = nullptr; + e2.outrec->back_edge = nullptr; + e2.outrec->pts = nullptr; + + if (IsOpenEnd(e1)) + { + e2.outrec->pts = e1.outrec->pts; + e1.outrec->pts = nullptr; + } + else + SetOwner(e2.outrec, e1.outrec); + + //and e1 and e2 are maxima and are about to be dropped from the Actives list. + e1.outrec = nullptr; + e2.outrec = nullptr; + } + + OutRec* ClipperBase::NewOutRec() + { + OutRec* result = new OutRec(); + result->idx = outrec_list_.size(); + outrec_list_.emplace_back(result); + result->pts = nullptr; + result->owner = nullptr; + result->polypath = nullptr; + result->is_open = false; + result->splits = nullptr; + return result; + } + + + OutPt* ClipperBase::AddOutPt(const Active& e, const Point64& pt) + { + OutPt* new_op = nullptr; + + //Outrec.OutPts: a circular doubly-linked-list of POutPt where ... + //op_front[.Prev]* ~~~> op_back & op_back == op_front.Next + OutRec* outrec = e.outrec; + bool to_front = IsFront(e); + OutPt* op_front = outrec->pts; + OutPt* op_back = op_front->next; + + if (to_front) + { + if (pt == op_front->pt) + return op_front; + } + else if (pt == op_back->pt) + return op_back; + + new_op = new OutPt(pt, outrec); + op_back->prev = new_op; + new_op->prev = op_front; + new_op->next = op_back; + op_front->next = new_op; + if (to_front) outrec->pts = new_op; + return new_op; + } + + void ClipperBase::CleanCollinear(OutRec* outrec) + { + outrec = GetRealOutRec(outrec); + if (!outrec || outrec->is_open) return; + if (!IsValidClosedPath(outrec->pts)) + { + DisposeOutPts(outrec); + return; + } + + OutPt* startOp = outrec->pts, * op2 = startOp; + for (; ; ) + { + //NB if preserveCollinear == true, then only remove 180 deg. spikes + if (IsCollinear(op2->prev->pt, op2->pt, op2->next->pt) && + (op2->pt == op2->prev->pt || + op2->pt == op2->next->pt || !preserve_collinear_ || + DotProduct(op2->prev->pt, op2->pt, op2->next->pt) < 0)) + { + + if (op2 == outrec->pts) outrec->pts = op2->prev; + + op2 = DisposeOutPt(op2); + if (!IsValidClosedPath(op2)) + { + DisposeOutPts(outrec); + return; + } + startOp = op2; + continue; + } + op2 = op2->next; + if (op2 == startOp) break; + } + FixSelfIntersects(outrec); + } + + void ClipperBase::DoSplitOp(OutRec* outrec, OutPt* splitOp) + { + // splitOp.prev -> splitOp && + // splitOp.next -> splitOp.next.next are intersecting + OutPt* prevOp = splitOp->prev; + OutPt* nextNextOp = splitOp->next->next; + outrec->pts = prevOp; + + Point64 ip; + GetSegmentIntersectPt(prevOp->pt, splitOp->pt, + splitOp->next->pt, nextNextOp->pt, ip); + +#ifdef USINGZ + if (zCallback_) zCallback_(prevOp->pt, splitOp->pt, + splitOp->next->pt, nextNextOp->pt, ip); +#endif + double area1 = Area(outrec->pts); + double absArea1 = std::fabs(area1); + if (absArea1 < 2) + { + DisposeOutPts(outrec); + return; + } + + 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; + } + + // 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. + if (absArea2 >= 1 && + (absArea2 > absArea1 || (area2 > 0) == (area1 > 0))) + { + OutRec* newOr = NewOutRec(); + newOr->owner = outrec->owner; + + splitOp->outrec = newOr; + splitOp->next->outrec = newOr; + OutPt* newOp = new OutPt(ip, newOr); + newOp->prev = splitOp->next; + newOp->next = splitOp; + newOr->pts = newOp; + splitOp->prev = newOp; + splitOp->next->next = newOp; + + if (using_polytree_) + { + if (Path1InsidePath2(prevOp, newOp)) + { + newOr->splits = new OutRecList(); + newOr->splits->emplace_back(outrec); + } + else + { + if (!outrec->splits) outrec->splits = new OutRecList(); + outrec->splits->emplace_back(newOr); + } + } + } + else + { + delete splitOp->next; + delete splitOp; + } + } + + void ClipperBase::FixSelfIntersects(OutRec* outrec) + { + OutPt* op2 = outrec->pts; + for (; ; ) + { + // triangles can't self-intersect + if (op2->prev == op2->next->next) break; + if (SegmentsIntersect(op2->prev->pt, + op2->pt, op2->next->pt, op2->next->next->pt)) + { + if (op2 == outrec->pts || op2->next == outrec->pts) + outrec->pts = outrec->pts->prev; + DoSplitOp(outrec, op2); + if (!outrec->pts) break; + op2 = outrec->pts; + continue; + } + else + op2 = op2->next; + + if (op2 == outrec->pts) break; + } + } + + + inline void UpdateOutrecOwner(OutRec* outrec) + { + OutPt* opCurr = outrec->pts; + for (; ; ) + { + opCurr->outrec = outrec; + opCurr = opCurr->next; + if (opCurr == outrec->pts) return; + } + } + + + OutPt* ClipperBase::StartOpenPath(Active& e, const Point64& pt) + { + OutRec* outrec = NewOutRec(); + outrec->is_open = true; + + if (e.wind_dx > 0) + { + outrec->front_edge = &e; + outrec->back_edge = nullptr; + } + else + { + outrec->front_edge = nullptr; + outrec->back_edge = &e; + } + + e.outrec = outrec; + + OutPt* op = new OutPt(pt, outrec); + outrec->pts = op; + return op; + } + + inline void 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 + } + + + inline void ClipperBase::UpdateEdgeIntoAEL(Active* e) + { + e->bot = e->top; + e->vertex_top = NextVertex(*e); + e->top = e->vertex_top->pt; + e->curr_x = e->bot.x; + SetDx(*e); + + if (IsJoined(*e)) Split(*e, e->bot); + + if (IsHorizontal(*e)) + { + if (!IsOpen(*e)) TrimHorz(*e, preserve_collinear_); + return; + } + + InsertScanline(e->top.y); + CheckJoinLeft(*e, e->bot); + CheckJoinRight(*e, e->bot, true); // (#500) + } + + 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; + } + + + void 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; + Active* edge_o, * edge_c; + if (IsOpen(e1)) + { + edge_o = &e1; + edge_c = &e2; + } + else + { + edge_o = &e2; + edge_c = &e1; + } + if (IsJoined(*edge_c)) Split(*edge_c, pt); // needed for safety + + if (abs(edge_c->wind_cnt) != 1) return; + switch (cliptype_) + { + case ClipType::Union: + if (!IsHotEdge(*edge_c)) return; + break; + default: + if (edge_c->local_min->polytype == PathType::Subject) + return; + } + + switch (fillrule_) + { + case FillRule::Positive: + if (edge_c->wind_cnt != 1) return; + break; + case FillRule::Negative: + if (edge_c->wind_cnt != -1) return; + break; + default: + if (std::abs(edge_c->wind_cnt) != 1) return; + } + +#ifdef USINGZ + OutPt* resultOp; +#endif + //toggle contribution ... + if (IsHotEdge(*edge_o)) + { +#ifdef USINGZ + resultOp = AddOutPt(*edge_o, pt); +#else + AddOutPt(*edge_o, pt); +#endif + if (IsFront(*edge_o)) edge_o->outrec->front_edge = nullptr; + else edge_o->outrec->back_edge = nullptr; + edge_o->outrec = nullptr; + } + + //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; + } + else +#ifdef USINGZ + resultOp = StartOpenPath(*edge_o, pt); +#else + StartOpenPath(*edge_o, pt); +#endif + } + else +#ifdef USINGZ + resultOp = StartOpenPath(*edge_o, pt); +#else + StartOpenPath(*edge_o, pt); +#endif + +#ifdef USINGZ + if (zCallback_) SetZ(*edge_o, *edge_c, resultOp->pt); +#endif + return; + } // end of an open path intersection + + //MANAGING CLOSED PATHS FROM HERE ON + + if (IsJoined(e1)) Split(e1, pt); + if (IsJoined(e2)) Split(e2, pt); + + //UPDATE WINDING COUNTS... + + int old_e1_windcnt, old_e2_windcnt; + if (e1.local_min->polytype == e2.local_min->polytype) + { + if (fillrule_ == FillRule::EvenOdd) + { + old_e1_windcnt = e1.wind_cnt; + e1.wind_cnt = e2.wind_cnt; + e2.wind_cnt = old_e1_windcnt; + } + else + { + if (e1.wind_cnt + e2.wind_dx == 0) + e1.wind_cnt = -e1.wind_cnt; + else + e1.wind_cnt += e2.wind_dx; + if (e2.wind_cnt - e1.wind_dx == 0) + e2.wind_cnt = -e2.wind_cnt; + else + e2.wind_cnt -= e1.wind_dx; + } + } + else + { + if (fillrule_ != FillRule::EvenOdd) + { + e1.wind_cnt2 += e2.wind_dx; + e2.wind_cnt2 -= e1.wind_dx; + } + else + { + e1.wind_cnt2 = (e1.wind_cnt2 == 0 ? 1 : 0); + e2.wind_cnt2 = (e2.wind_cnt2 == 0 ? 1 : 0); + } + } + + switch (fillrule_) + { + case FillRule::EvenOdd: + case FillRule::NonZero: + old_e1_windcnt = abs(e1.wind_cnt); + old_e2_windcnt = abs(e2.wind_cnt); + break; + default: + if (fillrule_ == fillpos) + { + old_e1_windcnt = e1.wind_cnt; + old_e2_windcnt = e2.wind_cnt; + } + else + { + old_e1_windcnt = -e1.wind_cnt; + old_e2_windcnt = -e2.wind_cnt; + } + break; + } + + const bool e1_windcnt_in_01 = old_e1_windcnt == 0 || old_e1_windcnt == 1; + const bool e2_windcnt_in_01 = old_e2_windcnt == 0 || old_e2_windcnt == 1; + + if ((!IsHotEdge(e1) && !e1_windcnt_in_01) || + (!IsHotEdge(e2) && !e2_windcnt_in_01)) + return; + + //NOW PROCESS THE INTERSECTION ... +#ifdef USINGZ + OutPt* resultOp = nullptr; +#endif + //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)) + { +#ifdef USINGZ + resultOp = AddLocalMaxPoly(e1, e2, pt); + if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt); +#else + AddLocalMaxPoly(e1, e2, 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 only touch at + //a common vertex (not at common edges). + +#ifdef USINGZ + resultOp = AddLocalMaxPoly(e1, e2, pt); + OutPt* op2 = AddLocalMinPoly(e1, e2, pt); + if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt); + if (zCallback_) SetZ(e1, e2, op2->pt); +#else + AddLocalMaxPoly(e1, e2, pt); + AddLocalMinPoly(e1, e2, pt); +#endif + } + else + { +#ifdef USINGZ + resultOp = AddOutPt(e1, pt); + OutPt* op2 = AddOutPt(e2, pt); + if (zCallback_) + { + SetZ(e1, e2, resultOp->pt); + SetZ(e1, e2, op2->pt); + } +#else + AddOutPt(e1, pt); + AddOutPt(e2, pt); +#endif + SwapOutrecs(e1, e2); + } + } + else if (IsHotEdge(e1)) + { +#ifdef USINGZ + resultOp = AddOutPt(e1, pt); + if (zCallback_) SetZ(e1, e2, resultOp->pt); +#else + AddOutPt(e1, pt); +#endif + SwapOutrecs(e1, e2); + } + else if (IsHotEdge(e2)) + { +#ifdef USINGZ + resultOp = AddOutPt(e2, pt); + if (zCallback_) SetZ(e1, e2, resultOp->pt); +#else + AddOutPt(e2, 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)) + { +#ifdef USINGZ + resultOp = AddLocalMinPoly(e1, e2, pt, false); + if (zCallback_) SetZ(e1, e2, resultOp->pt); +#else + AddLocalMinPoly(e1, e2, pt, false); +#endif + } + else if (old_e1_windcnt == 1 && old_e2_windcnt == 1) + { +#ifdef USINGZ + resultOp = nullptr; +#endif + switch (cliptype_) + { + case ClipType::Union: + if (e1Wc2 <= 0 && e2Wc2 <= 0) +#ifdef USINGZ + resultOp = AddLocalMinPoly(e1, e2, pt, false); +#else + AddLocalMinPoly(e1, e2, pt, false); +#endif + break; + case ClipType::Difference: + if (((GetPolyType(e1) == PathType::Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((GetPolyType(e1) == PathType::Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + { +#ifdef USINGZ + resultOp = AddLocalMinPoly(e1, e2, pt, false); +#else + AddLocalMinPoly(e1, e2, pt, false); +#endif + } + break; + case ClipType::Xor: +#ifdef USINGZ + resultOp = AddLocalMinPoly(e1, e2, pt, false); +#else + AddLocalMinPoly(e1, e2, pt, false); +#endif + break; + default: + if (e1Wc2 > 0 && e2Wc2 > 0) +#ifdef USINGZ + resultOp = AddLocalMinPoly(e1, e2, pt, false); +#else + AddLocalMinPoly(e1, e2, pt, false); +#endif + break; + } +#ifdef USINGZ + if (resultOp && zCallback_) SetZ(e1, e2, resultOp->pt); +#endif + } + } + } + + inline void ClipperBase::DeleteFromAEL(Active& e) + { + Active* prev = e.prev_in_ael; + Active* next = e.next_in_ael; + if (!prev && !next && (&e != actives_)) return; // already deleted + if (prev) + prev->next_in_ael = next; + else + actives_ = next; + if (next) next->prev_in_ael = prev; + delete& e; + } + + + inline void ClipperBase::AdjustCurrXAndCopyToSEL(const int64_t top_y) + { + Active* e = actives_; + sel_ = e; + while (e) + { + e->prev_in_sel = e->prev_in_ael; + e->next_in_sel = e->next_in_ael; + e->jump = e->next_in_sel; + if (e->join_with == JoinWith::Left) + e->curr_x = e->prev_in_ael->curr_x; // also avoids complications + else + e->curr_x = TopX(*e, top_y); + e = e->next_in_ael; + } + } + + bool ClipperBase::ExecuteInternal(ClipType ct, FillRule fillrule, bool use_polytrees) + { + cliptype_ = ct; + fillrule_ = fillrule; + using_polytree_ = use_polytrees; + Reset(); + int64_t y; + if (ct == ClipType::NoClip || !PopScanline(y)) return true; + + while (succeeded_) + { + InsertLocalMinimaIntoAEL(y); + Active* e; + while (PopHorz(e)) DoHorizontal(*e); + if (horz_seg_list_.size() > 0) + { + ConvertHorzSegsToJoins(); + horz_seg_list_.clear(); + } + bot_y_ = y; // bot_y_ == bottom of scanbeam + if (!PopScanline(y)) break; // y new top of scanbeam + DoIntersections(y); + DoTopOfScanbeam(y); + while (PopHorz(e)) DoHorizontal(*e); + } + if (succeeded_) ProcessHorzJoins(); + return succeeded_; + } + + inline void FixOutRecPts(OutRec* outrec) + { + OutPt* op = outrec->pts; + do { + op->outrec = outrec; + op = op->next; + } while (op != outrec->pts); + } + + inline bool SetHorzSegHeadingForward(HorzSegment& hs, OutPt* opP, OutPt* opN) + { + if (opP->pt.x == opN->pt.x) return false; + if (opP->pt.x < opN->pt.x) + { + hs.left_op = opP; + hs.right_op = opN; + hs.left_to_right = true; + } + else + { + hs.left_op = opN; + hs.right_op = opP; + hs.left_to_right = false; + } + return true; + } + + inline bool UpdateHorzSegment(HorzSegment& hs) + { + OutPt* op = hs.left_op; + OutRec* outrec = GetRealOutRec(op->outrec); + bool outrecHasEdges = outrec->front_edge; + int64_t curr_y = op->pt.y; + OutPt* opP = op, * opN = op; + if (outrecHasEdges) + { + OutPt* opA = outrec->pts, * opZ = opA->next; + while (opP != opZ && opP->prev->pt.y == curr_y) + opP = opP->prev; + while (opN != opA && opN->next->pt.y == curr_y) + opN = opN->next; + } + else + { + while (opP->prev != opN && opP->prev->pt.y == curr_y) + opP = opP->prev; + while (opN->next != opP && opN->next->pt.y == curr_y) + opN = opN->next; + } + bool result = + SetHorzSegHeadingForward(hs, opP, opN) && + !hs.left_op->horz; + + if (result) + hs.left_op->horz = &hs; + else + hs.right_op = nullptr; // (for sorting) + return result; + } + + void ClipperBase::ConvertHorzSegsToJoins() + { + auto j = std::count_if(horz_seg_list_.begin(), + horz_seg_list_.end(), + [](HorzSegment& hs) { return UpdateHorzSegment(hs); }); + if (j < 2) return; + + std::stable_sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter()); + + HorzSegmentList::iterator hs1 = horz_seg_list_.begin(), hs2; + HorzSegmentList::iterator hs_end = hs1 +j; + HorzSegmentList::iterator hs_end1 = hs_end - 1; + + for (; hs1 != hs_end1; ++hs1) + { + for (hs2 = hs1 + 1; hs2 != hs_end; ++hs2) + { + if ((hs2->left_op->pt.x >= hs1->right_op->pt.x) || + (hs2->left_to_right == hs1->left_to_right) || + (hs2->right_op->pt.x <= hs1->left_op->pt.x)) continue; + int64_t curr_y = hs1->left_op->pt.y; + if (hs1->left_to_right) + { + while (hs1->left_op->next->pt.y == curr_y && + hs1->left_op->next->pt.x <= hs2->left_op->pt.x) + hs1->left_op = hs1->left_op->next; + while (hs2->left_op->prev->pt.y == curr_y && + hs2->left_op->prev->pt.x <= hs1->left_op->pt.x) + hs2->left_op = hs2->left_op->prev; + HorzJoin join = HorzJoin( + DuplicateOp(hs1->left_op, true), + DuplicateOp(hs2->left_op, false)); + horz_join_list_.emplace_back(join); + } + else + { + while (hs1->left_op->prev->pt.y == curr_y && + hs1->left_op->prev->pt.x <= hs2->left_op->pt.x) + hs1->left_op = hs1->left_op->prev; + while (hs2->left_op->next->pt.y == curr_y && + hs2->left_op->next->pt.x <= hs1->left_op->pt.x) + hs2->left_op = hs2->left_op->next; + HorzJoin join = HorzJoin( + DuplicateOp(hs2->left_op, true), + DuplicateOp(hs1->left_op, false)); + horz_join_list_.emplace_back(join); + } + } + } + } + + void MoveSplits(OutRec* fromOr, OutRec* toOr) + { + if (!fromOr->splits) return; + if (!toOr->splits) toOr->splits = new OutRecList(); + OutRecList::iterator orIter = fromOr->splits->begin(); + for (; orIter != fromOr->splits->end(); ++orIter) + toOr->splits->emplace_back(*orIter); + fromOr->splits->clear(); + } + + + void ClipperBase::ProcessHorzJoins() + { + for (const HorzJoin& j : horz_join_list_) + { + OutRec* or1 = GetRealOutRec(j.op1->outrec); + OutRec* or2 = GetRealOutRec(j.op2->outrec); + + OutPt* op1b = j.op1->next; + OutPt* op2b = j.op2->prev; + j.op1->next = j.op2; + j.op2->prev = j.op1; + op1b->prev = op2b; + op2b->next = op1b; + + if (or1 == or2) // 'join' is really a split + { + or2 = NewOutRec(); + or2->pts = op1b; + FixOutRecPts(or2); + + //if or1->pts has moved to or2 then update or1->pts!! + if (or1->pts->outrec == or2) + { + or1->pts = j.op1; + or1->pts->outrec = or1; + } + + if (using_polytree_) //#498, #520, #584, D#576, #618 + { + if (Path1InsidePath2(or1->pts, or2->pts)) + { + //swap or1's & or2's pts + OutPt* tmp = or1->pts; + or1->pts = or2->pts; + or2->pts = tmp; + FixOutRecPts(or1); + FixOutRecPts(or2); + //or2 is now inside or1 + or2->owner = or1; + } + else if (Path1InsidePath2(or2->pts, or1->pts)) + { + or2->owner = or1; + } + else + or2->owner = or1->owner; + + if (!or1->splits) or1->splits = new OutRecList(); + or1->splits->emplace_back(or2); + } + else + or2->owner = or1; + } + else + { + or2->pts = nullptr; + if (using_polytree_) + { + SetOwner(or2, or1); + MoveSplits(or2, or1); //#618 + } + else + or2->owner = or1; + } + } + } + + void ClipperBase::DoIntersections(const int64_t top_y) + { + if (BuildIntersectList(top_y)) + { + ProcessIntersectList(); + intersect_nodes_.clear(); + } + } + + void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y) + { + Point64 ip; + if (!GetSegmentIntersectPt(e1.bot, e1.top, e2.bot, e2.top, ip)) + ip = Point64(e1.curr_x, top_y); //parallel edges + + //rounding errors can occasionally place the calculated intersection + //point either below or above the scanbeam, so check and correct ... + if (ip.y > bot_y_ || ip.y < top_y) + { + double abs_dx1 = std::fabs(e1.dx); + double abs_dx2 = std::fabs(e2.dx); + if (abs_dx1 > 100 && abs_dx2 > 100) + { + if (abs_dx1 > abs_dx2) + ip = GetClosestPointOnSegment(ip, e1.bot, e1.top); + else + ip = GetClosestPointOnSegment(ip, e2.bot, e2.top); + } + else if (abs_dx1 > 100) + ip = GetClosestPointOnSegment(ip, e1.bot, e1.top); + else if (abs_dx2 > 100) + ip = GetClosestPointOnSegment(ip, e2.bot, e2.top); + else + { + if (ip.y < top_y) ip.y = top_y; + else ip.y = bot_y_; + if (abs_dx1 < abs_dx2) ip.x = TopX(e1, ip.y); + else ip.x = TopX(e2, ip.y); + } + } + intersect_nodes_.emplace_back(&e1, &e2, ip); + } + + bool ClipperBase::BuildIntersectList(const int64_t top_y) + { + if (!actives_ || !actives_->next_in_ael) return false; + + //Calculate edge positions at the top of the current scanbeam, and from this + //we will determine the intersections required to reach these new positions. + AdjustCurrXAndCopyToSEL(top_y); + //Find all edge intersections in the current scanbeam using a stable merge + //sort that ensures only adjacent edges are intersecting. Intersect info is + //stored in FIntersectList ready to be processed in ProcessIntersectList. + //Re merge sorts see https://stackoverflow.com/a/46319131/359538 + + Active* left = sel_, * right, * l_end, * r_end, * curr_base, * tmp; + + while (left && left->jump) + { + Active* prev_base = nullptr; + while (left && left->jump) + { + curr_base = left; + right = left->jump; + l_end = right; + r_end = right->jump; + left->jump = r_end; + while (left != l_end && right != r_end) + { + if (right->curr_x < left->curr_x) + { + tmp = right->prev_in_sel; + for (; ; ) + { + AddNewIntersectNode(*tmp, *right, top_y); + if (tmp == left) break; + tmp = tmp->prev_in_sel; + } + + tmp = right; + right = ExtractFromSEL(tmp); + l_end = right; + Insert1Before2InSEL(tmp, left); + if (left == curr_base) + { + curr_base = tmp; + curr_base->jump = r_end; + if (!prev_base) sel_ = curr_base; + else prev_base->jump = curr_base; + } + } + else left = left->next_in_sel; + } + prev_base = curr_base; + left = r_end; + } + left = sel_; + } + return intersect_nodes_.size() > 0; + } + + void ClipperBase::ProcessIntersectList() + { + //We now have a list of intersections required so that edges will be + //correctly positioned at the top of the scanbeam. However, it's important + //that edge intersections are processed from the bottom up, but it's also + //crucial that intersections only occur between adjacent edges. + + //First we do a quicksort so intersections proceed in a bottom up order ... + std::sort(intersect_nodes_.begin(), intersect_nodes_.end(), IntersectListSort); + //Now as we process these intersections, we must sometimes adjust the order + //to ensure that intersecting edges are always adjacent ... + + IntersectNodeList::iterator node_iter, node_iter2; + for (node_iter = intersect_nodes_.begin(); + node_iter != intersect_nodes_.end(); ++node_iter) + { + if (!EdgesAdjacentInAEL(*node_iter)) + { + node_iter2 = node_iter + 1; + while (!EdgesAdjacentInAEL(*node_iter2)) ++node_iter2; + std::swap(*node_iter, *node_iter2); + } + + IntersectNode& node = *node_iter; + IntersectEdges(*node.edge1, *node.edge2, node.pt); + SwapPositionsInAEL(*node.edge1, *node.edge2); + + node.edge1->curr_x = node.pt.x; + node.edge2->curr_x = node.pt.x; + CheckJoinLeft(*node.edge2, node.pt, true); + CheckJoinRight(*node.edge1, node.pt, true); + } + } + + void ClipperBase::SwapPositionsInAEL(Active& e1, Active& e2) + { + //preconditon: e1 must be immediately to the left of e2 + Active* next = e2.next_in_ael; + if (next) next->prev_in_ael = &e1; + Active* prev = e1.prev_in_ael; + if (prev) prev->next_in_ael = &e2; + e2.prev_in_ael = prev; + e2.next_in_ael = &e1; + e1.prev_in_ael = &e2; + e1.next_in_ael = next; + if (!e2.prev_in_ael) actives_ = &e2; + } + + inline OutPt* GetLastOp(const Active& hot_edge) + { + OutRec* outrec = hot_edge.outrec; + OutPt* result = outrec->pts; + if (&hot_edge != outrec->front_edge) + result = result->next; + return result; + } + + void ClipperBase::AddTrialHorzJoin(OutPt* op) + { + if (op->outrec->is_open) return; + horz_seg_list_.emplace_back(op); + } + + bool ClipperBase::ResetHorzDirection(const Active& horz, + const Vertex* max_vertex, int64_t& horz_left, int64_t& horz_right) + { + if (horz.bot.x == horz.top.x) + { + //the horizontal edge is going nowhere ... + horz_left = horz.curr_x; + horz_right = horz.curr_x; + Active* e = horz.next_in_ael; + while (e && e->vertex_top != max_vertex) e = e->next_in_ael; + return e != nullptr; + } + else if (horz.curr_x < horz.top.x) + { + horz_left = horz.curr_x; + horz_right = horz.top.x; + return true; + } + else + { + horz_left = horz.top.x; + horz_right = horz.curr_x; + return false; // right to left + } + } + + void ClipperBase::DoHorizontal(Active& horz) + /******************************************************************************* + * Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or * + * bottom of a scanbeam) are processed as if layered.The order in which HEs * + * are processed doesn't matter. HEs intersect with the bottom vertices of * + * other HEs[#] and with non-horizontal edges [*]. Once these intersections * + * are completed, intermediate HEs are 'promoted' to the next edge in their * + * bounds, and they in turn may be intersected[%] by other HEs. * + * * + * eg: 3 horizontals at a scanline: / | / / * + * | / | (HE3)o ========%========== o * + * o ======= o(HE2) / | / / * + * o ============#=========*======*========#=========o (HE1) * + * / | / | / * + *******************************************************************************/ + { + Point64 pt; + bool horzIsOpen = IsOpen(horz); + int64_t y = horz.bot.y; + Vertex* vertex_max; + if (horzIsOpen) + vertex_max = GetCurrYMaximaVertex_Open(horz); + else + vertex_max = GetCurrYMaximaVertex(horz); + + //// remove 180 deg.spikes and also simplify + //// consecutive horizontals when PreserveCollinear = true + //if (!horzIsOpen && vertex_max != horz.vertex_top) + // TrimHorz(horz, PreserveCollinear); + + int64_t horz_left, horz_right; + bool is_left_to_right = + ResetHorzDirection(horz, vertex_max, horz_left, horz_right); + + if (IsHotEdge(horz)) + { +#ifdef USINGZ + OutPt* op = AddOutPt(horz, Point64(horz.curr_x, y, horz.bot.z)); +#else + OutPt* op = AddOutPt(horz, Point64(horz.curr_x, y)); +#endif + AddTrialHorzJoin(op); + } + + while (true) // loop through consec. horizontal edges + { + Active* e; + if (is_left_to_right) e = horz.next_in_ael; + else e = horz.prev_in_ael; + + while (e) + { + if (e->vertex_top == vertex_max) + { + if (IsHotEdge(horz) && IsJoined(*e)) + Split(*e, e->top); + + //if (IsHotEdge(horz) != IsHotEdge(*e)) + // DoError(undefined_error_i); + + if (IsHotEdge(horz)) + { + while (horz.vertex_top != vertex_max) + { + AddOutPt(horz, horz.top); + UpdateEdgeIntoAEL(&horz); + } + if (is_left_to_right) + AddLocalMaxPoly(horz, *e, horz.top); + else + AddLocalMaxPoly(*e, horz, horz.top); + } + DeleteFromAEL(*e); + DeleteFromAEL(horz); + return; + } + + //if horzEdge is a maxima, keep going until we reach + //its maxima pair, otherwise check for break conditions + if (vertex_max != horz.vertex_top || IsOpenEnd(horz)) + { + //otherwise stop when 'ae' is beyond the end of the horizontal line + if ((is_left_to_right && e->curr_x > horz_right) || + (!is_left_to_right && e->curr_x < horz_left)) break; + + if (e->curr_x == horz.top.x && !IsHorizontal(*e)) + { + pt = NextVertex(horz)->pt; + if (is_left_to_right) + { + //with open paths we'll only break once past horz's end + if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e)) + { + if (TopX(*e, pt.y) > pt.x) break; + } + //otherwise we'll only break when horz's outslope is greater than e's + else if (TopX(*e, pt.y) >= pt.x) break; + } + else + { + if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e)) + { + if (TopX(*e, pt.y) < pt.x) break; + } + else if (TopX(*e, pt.y) <= pt.x) break; + } + } + } + + pt = Point64(e->curr_x, horz.bot.y); + if (is_left_to_right) + { + IntersectEdges(horz, *e, pt); + SwapPositionsInAEL(horz, *e); + CheckJoinLeft(*e, pt); + horz.curr_x = e->curr_x; + e = horz.next_in_ael; + } + else + { + IntersectEdges(*e, horz, pt); + SwapPositionsInAEL(*e, horz); + CheckJoinRight(*e, pt); + horz.curr_x = e->curr_x; + e = horz.prev_in_ael; + } + + if (horz.outrec) + { + //nb: The outrec containing the op returned by IntersectEdges + //above may no longer be associated with horzEdge. + AddTrialHorzJoin(GetLastOp(horz)); + } + } + + //check if we've finished with (consecutive) horizontals ... + if (horzIsOpen && IsOpenEnd(horz)) // ie open at top + { + if (IsHotEdge(horz)) + { + AddOutPt(horz, horz.top); + if (IsFront(horz)) + horz.outrec->front_edge = nullptr; + else + horz.outrec->back_edge = nullptr; + horz.outrec = nullptr; + } + DeleteFromAEL(horz); + return; + } + else if (NextVertex(horz)->pt.y != horz.top.y) + break; + + //still more horizontals in bound to process ... + if (IsHotEdge(horz)) + AddOutPt(horz, horz.top); + UpdateEdgeIntoAEL(&horz); + + is_left_to_right = + ResetHorzDirection(horz, vertex_max, horz_left, horz_right); + } + + if (IsHotEdge(horz)) + { + OutPt* op = AddOutPt(horz, horz.top); + AddTrialHorzJoin(op); + } + + UpdateEdgeIntoAEL(&horz); // end of an intermediate horiz. + } + + void ClipperBase::DoTopOfScanbeam(const int64_t y) + { + sel_ = nullptr; // sel_ is reused to flag horizontals (see PushHorz below) + Active* e = actives_; + while (e) + { + //nb: 'e' will never be horizontal here + if (e->top.y == y) + { + e->curr_x = e->top.x; + if (IsMaxima(*e)) + { + e = DoMaxima(*e); // TOP OF BOUND (MAXIMA) + continue; + } + else + { + //INTERMEDIATE VERTEX ... + if (IsHotEdge(*e)) AddOutPt(*e, e->top); + UpdateEdgeIntoAEL(e); + if (IsHorizontal(*e)) + PushHorz(*e); // horizontals are processed later + } + } + else // i.e. not the top of the edge + e->curr_x = TopX(*e, y); + + e = e->next_in_ael; + } + } + + + Active* ClipperBase::DoMaxima(Active& e) + { + Active* next_e, * prev_e, * max_pair; + prev_e = e.prev_in_ael; + next_e = e.next_in_ael; + if (IsOpenEnd(e)) + { + if (IsHotEdge(e)) AddOutPt(e, e.top); + if (!IsHorizontal(e)) + { + if (IsHotEdge(e)) + { + if (IsFront(e)) + e.outrec->front_edge = nullptr; + else + e.outrec->back_edge = nullptr; + e.outrec = nullptr; + } + DeleteFromAEL(e); + } + return next_e; + } + + max_pair = GetMaximaPair(e); + if (!max_pair) return next_e; // eMaxPair is horizontal + + if (IsJoined(e)) Split(e, e.top); + if (IsJoined(*max_pair)) Split(*max_pair, max_pair->top); + + //only non-horizontal maxima here. + //process any edges between maxima pair ... + while (next_e != max_pair) + { + IntersectEdges(e, *next_e, e.top); + SwapPositionsInAEL(e, *next_e); + next_e = e.next_in_ael; + } + + if (IsOpen(e)) + { + if (IsHotEdge(e)) + AddLocalMaxPoly(e, *max_pair, e.top); + DeleteFromAEL(*max_pair); + DeleteFromAEL(e); + return (prev_e ? prev_e->next_in_ael : actives_); + } + + // e.next_in_ael== max_pair ... + if (IsHotEdge(e)) + AddLocalMaxPoly(e, *max_pair, e.top); + + DeleteFromAEL(e); + DeleteFromAEL(*max_pair); + return (prev_e ? prev_e->next_in_ael : actives_); + } + + void ClipperBase::Split(Active& e, const Point64& pt) + { + if (e.join_with == JoinWith::Right) + { + e.join_with = JoinWith::NoJoin; + e.next_in_ael->join_with = JoinWith::NoJoin; + AddLocalMinPoly(e, *e.next_in_ael, pt, true); + } + else + { + e.join_with = JoinWith::NoJoin; + e.prev_in_ael->join_with = JoinWith::NoJoin; + AddLocalMinPoly(*e.prev_in_ael, e, pt, true); + } + } + + void ClipperBase::CheckJoinLeft(Active& e, + const Point64& pt, bool check_curr_x) + { + Active* prev = e.prev_in_ael; + if (!prev || + !IsHotEdge(e) || !IsHotEdge(*prev) || + IsHorizontal(e) || IsHorizontal(*prev) || + IsOpen(e) || IsOpen(*prev) ) return; + if ((pt.y < e.top.y + 2 || pt.y < prev->top.y + 2) && + ((e.bot.y > pt.y) || (prev->bot.y > pt.y))) return; // avoid trivial joins + + if (check_curr_x) + { + if (PerpendicDistFromLineSqrd(pt, prev->bot, prev->top) > 0.25) return; + } + else if (e.curr_x != prev->curr_x) return; + if (!IsCollinear(e.top, pt, prev->top)) return; + + if (e.outrec->idx == prev->outrec->idx) + AddLocalMaxPoly(*prev, e, pt); + else if (e.outrec->idx < prev->outrec->idx) + JoinOutrecPaths(e, *prev); + else + JoinOutrecPaths(*prev, e); + prev->join_with = JoinWith::Right; + e.join_with = JoinWith::Left; + } + + void ClipperBase::CheckJoinRight(Active& e, + const Point64& pt, bool check_curr_x) + { + Active* next = e.next_in_ael; + if (!next || + !IsHotEdge(e) || !IsHotEdge(*next) || + IsHorizontal(e) || IsHorizontal(*next) || + IsOpen(e) || IsOpen(*next)) return; + if ((pt.y < e.top.y +2 || pt.y < next->top.y +2) && + ((e.bot.y > pt.y) || (next->bot.y > pt.y))) return; // avoid trivial joins + + if (check_curr_x) + { + if (PerpendicDistFromLineSqrd(pt, next->bot, next->top) > 0.35) return; + } + else if (e.curr_x != next->curr_x) return; + if (!IsCollinear(e.top, pt, next->top)) return; + + if (e.outrec->idx == next->outrec->idx) + AddLocalMaxPoly(e, *next, pt); + else if (e.outrec->idx < next->outrec->idx) + JoinOutrecPaths(e, *next); + else + JoinOutrecPaths(*next, e); + + e.join_with = JoinWith::Right; + next->join_with = JoinWith::Left; + } + + inline bool GetHorzExtendedHorzSeg(OutPt*& op, OutPt*& op2) + { + OutRec* outrec = GetRealOutRec(op->outrec); + op2 = op; + if (outrec->front_edge) + { + while (op->prev != outrec->pts && + op->prev->pt.y == op->pt.y) op = op->prev; + while (op2 != outrec->pts && + op2->next->pt.y == op2->pt.y) op2 = op2->next; + return op2 != op; + } + else + { + while (op->prev != op2 && op->prev->pt.y == op->pt.y) + op = op->prev; + while (op2->next != op && op2->next->pt.y == op2->pt.y) + op2 = op2->next; + return op2 != op && op2->next != op; + } + } + + bool BuildPath64(OutPt* op, bool reverse, bool isOpen, Path64& path) + { + if (!op || op->next == op || (!isOpen && op->next == op->prev)) + return false; + + path.resize(0); + Point64 lastPt; + OutPt* op2; + if (reverse) + { + lastPt = op->pt; + op2 = op->prev; + } + else + { + op = op->next; + lastPt = op->pt; + op2 = op->next; + } + path.emplace_back(lastPt); + + while (op2 != op) + { + if (op2->pt != lastPt) + { + lastPt = op2->pt; + path.emplace_back(lastPt); + } + if (reverse) + op2 = op2->prev; + else + op2 = op2->next; + } + + if (!isOpen && path.size() == 3 && IsVerySmallTriangle(*op2)) return false; + else return true; + } + + bool ClipperBase::CheckBounds(OutRec* outrec) + { + if (!outrec->pts) return false; + if (!outrec->bounds.IsEmpty()) return true; + CleanCollinear(outrec); + if (!outrec->pts || + !BuildPath64(outrec->pts, reverse_solution_, false, outrec->path)){ + return false;} + outrec->bounds = GetBounds(outrec->path); + return true; + } + + bool ClipperBase::CheckSplitOwner(OutRec* outrec, OutRecList* splits) + { + for (auto split : *splits) + { + split = GetRealOutRec(split); + if(!split || split == outrec || split->recursive_split == outrec) continue; + split->recursive_split = outrec; // prevent infinite loops + + if (split->splits && CheckSplitOwner(outrec, split->splits)) + return true; + else if (CheckBounds(split) && + IsValidOwner(outrec, split) && + split->bounds.Contains(outrec->bounds) && + Path1InsidePath2(outrec->pts, split->pts)) + { + outrec->owner = split; //found in split + return true; + } + } + return false; + } + + void ClipperBase::RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath) + { + // pre-condition: outrec will have valid bounds + // post-condition: if a valid path, outrec will have a polypath + + if (outrec->polypath || outrec->bounds.IsEmpty()) return; + + while (outrec->owner) + { + if (outrec->owner->splits && CheckSplitOwner(outrec, outrec->owner->splits)) break; + if (outrec->owner->pts && CheckBounds(outrec->owner) && + outrec->owner->bounds.Contains(outrec->bounds) && + Path1InsidePath2(outrec->pts, outrec->owner->pts)) break; + outrec->owner = outrec->owner->owner; + } + + if (outrec->owner) + { + if (!outrec->owner->polypath) + RecursiveCheckOwners(outrec->owner, polypath); + outrec->polypath = outrec->owner->polypath->AddChild(outrec->path); + } + else + outrec->polypath = polypath->AddChild(outrec->path); + } + + void Clipper64::BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen) + { + solutionClosed.resize(0); + solutionClosed.reserve(outrec_list_.size()); + if (solutionOpen) + { + solutionOpen->resize(0); + solutionOpen->reserve(outrec_list_.size()); + } + + // nb: outrec_list_.size() may change in the following + // while loop because polygons may be split during + // calls to CleanCollinear which calls FixSelfIntersects + for (size_t i = 0; i < outrec_list_.size(); ++i) + { + OutRec* outrec = outrec_list_[i]; + if (outrec->pts == nullptr) continue; + + Path64 path; + if (solutionOpen && outrec->is_open) + { + if (BuildPath64(outrec->pts, reverse_solution_, true, path)) + solutionOpen->emplace_back(std::move(path)); + } + else + { + // nb: CleanCollinear can add to outrec_list_ + CleanCollinear(outrec); + //closed paths should always return a Positive orientation + if (BuildPath64(outrec->pts, reverse_solution_, 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()); + + // outrec_list_.size() is not static here because + // CheckBounds below can indirectly add additional + // OutRec (via FixOutRecPts & CleanCollinear) + for (size_t i = 0; i < outrec_list_.size(); ++i) + { + OutRec* outrec = outrec_list_[i]; + if (!outrec || !outrec->pts) continue; + if (outrec->is_open) + { + Path64 path; + if (BuildPath64(outrec->pts, reverse_solution_, true, path)) + open_paths.emplace_back(std::move(path)); + continue; + } + + if (CheckBounds(outrec)) + RecursiveCheckOwners(outrec, &polytree); + } + } + + bool BuildPathD(OutPt* op, bool reverse, bool isOpen, PathD& path, double inv_scale) + { + if (!op || op->next == op || (!isOpen && op->next == op->prev)) + return false; + + path.resize(0); + Point64 lastPt; + OutPt* op2; + if (reverse) + { + lastPt = op->pt; + op2 = op->prev; + } + else + { + op = op->next; + lastPt = op->pt; + op2 = op->next; + } +#ifdef USINGZ + path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z); +#else + path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale); +#endif + + while (op2 != op) + { + if (op2->pt != lastPt) + { + lastPt = op2->pt; +#ifdef USINGZ + path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z); +#else + path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale); +#endif + + } + if (reverse) + op2 = op2->prev; + else + op2 = op2->next; + } + if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false; + return true; + } + + void ClipperD::BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen) + { + solutionClosed.resize(0); + solutionClosed.reserve(outrec_list_.size()); + if (solutionOpen) + { + solutionOpen->resize(0); + solutionOpen->reserve(outrec_list_.size()); + } + + // outrec_list_.size() is not static here because + // CleanCollinear below can indirectly add additional + // OutRec (via FixOutRecPts) + for (std::size_t i = 0; i < outrec_list_.size(); ++i) + { + OutRec* outrec = outrec_list_[i]; + if (outrec->pts == nullptr) continue; + + PathD path; + if (solutionOpen && outrec->is_open) + { + if (BuildPathD(outrec->pts, reverse_solution_, true, path, invScale_)) + solutionOpen->emplace_back(std::move(path)); + } + else + { + CleanCollinear(outrec); + //closed paths should always return a Positive orientation + if (BuildPathD(outrec->pts, reverse_solution_, 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()); + + // outrec_list_.size() is not static here because + // BuildPathD below can indirectly add additional OutRec //#607 + for (size_t i = 0; i < outrec_list_.size(); ++i) + { + OutRec* outrec = outrec_list_[i]; + if (!outrec || !outrec->pts) continue; + if (outrec->is_open) + { + PathD path; + if (BuildPathD(outrec->pts, reverse_solution_, true, path, invScale_)) + open_paths.emplace_back(std::move(path)); + continue; + } + + if (CheckBounds(outrec)) + RecursiveCheckOwners(outrec, &polytree); + } + } + +} // namespace clipper2lib diff --git a/src/clipper2/Clipper2Lib/src/clipper.offset.cpp b/src/clipper2/Clipper2Lib/src/clipper.offset.cpp new file mode 100644 index 0000000000..c00a345c01 --- /dev/null +++ b/src/clipper2/Clipper2Lib/src/clipper.offset.cpp @@ -0,0 +1,658 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 22 January 2025 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2025 * +* Purpose : Path Offset (Inflate/Shrink) * +* License : https://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#include "clipper2/clipper.h" +#include "clipper2/clipper.offset.h" + +#ifdef USINGZ +namespace Clipper2Lib_Z { +#else +namespace Clipper2Lib { +#endif + +const double floating_point_tolerance = 1e-12; + +// Clipper2 approximates arcs by using series of relatively short straight +//line segments. And logically, shorter line segments will produce better arc +// approximations. But very short segments can degrade performance, usually +// with little or no discernable improvement in curve quality. Very short +// segments can even detract from curve quality, due to the effects of integer +// rounding. Since there isn't an optimal number of line segments for any given +// arc radius (that perfectly balances curve approximation with performance), +// arc tolerance is user defined. Nevertheless, when the user doesn't define +// an arc tolerance (ie leaves alone the 0 default value), the calculated +// default arc tolerance (offset_radius / 500) generally produces good (smooth) +// arc approximations without producing excessively small segment lengths. +// See also: https://www.angusj.com/clipper2/Docs/Trigonometry.htm +const double arc_const = 0.002; // <-- 1/500 + + +//------------------------------------------------------------------------------ +// Miscellaneous methods +//------------------------------------------------------------------------------ + +std::optional GetLowestClosedPathIdx(const Paths64& paths) +{ + std::optional result; + Point64 botPt = Point64(INT64_MAX, INT64_MIN); + for (size_t i = 0; i < paths.size(); ++i) + { + for (const Point64& pt : paths[i]) + { + if ((pt.y < botPt.y) || + ((pt.y == botPt.y) && (pt.x >= botPt.x))) continue; + result = i; + botPt.x = pt.x; + botPt.y = pt.y; + } + } + return result; +} + +inline double Hypot(double x, double y) +{ + // given that this is an internal function, and given the x and y parameters + // will always be coordinate values (or the difference between coordinate values), + // x and y should always be within INT64_MIN to INT64_MAX. Consequently, + // there should be no risk that the following computation will overflow + // see https://stackoverflow.com/a/32436148/359538 + return std::sqrt(x * x + y * y); +} + +static PointD GetUnitNormal(const Point64& pt1, const Point64& pt2) +{ + if (pt1 == pt2) return PointD(0.0, 0.0); + double dx = static_cast(pt2.x - pt1.x); + double dy = static_cast(pt2.y - pt1.y); + double inverse_hypot = 1.0 / Hypot(dx, dy); + dx *= inverse_hypot; + dy *= inverse_hypot; + return PointD(dy, -dx); +} + +inline bool AlmostZero(double value, double epsilon = 0.001) +{ + return std::fabs(value) < epsilon; +} + +inline PointD NormalizeVector(const PointD& vec) +{ + double h = Hypot(vec.x, vec.y); + if (AlmostZero(h)) return PointD(0,0); + double inverseHypot = 1 / h; + return PointD(vec.x * inverseHypot, vec.y * inverseHypot); +} + +inline PointD GetAvgUnitVector(const PointD& vec1, const PointD& vec2) +{ + return NormalizeVector(PointD(vec1.x + vec2.x, vec1.y + vec2.y)); +} + +inline bool IsClosedPath(EndType et) +{ + return et == EndType::Polygon || et == EndType::Joined; +} + +static inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta) +{ +#ifdef USINGZ + return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z); +#else + return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta); +#endif +} + +inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta) +{ +#ifdef USINGZ + return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z); +#else + return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta); +#endif +} + +inline void NegatePath(PathD& path) +{ + for (PointD& pt : path) + { + pt.x = -pt.x; + pt.y = -pt.y; +#ifdef USINGZ + pt.z = pt.z; +#endif + } +} + + +//------------------------------------------------------------------------------ +// ClipperOffset::Group methods +//------------------------------------------------------------------------------ + +ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType _end_type): + paths_in(_paths), join_type(_join_type), end_type(_end_type) +{ + bool is_joined = + (end_type == EndType::Polygon) || + (end_type == EndType::Joined); + for (Path64& p: paths_in) + StripDuplicates(p, is_joined); + + if (end_type == EndType::Polygon) + { + lowest_path_idx = GetLowestClosedPathIdx(paths_in); + // the lowermost path must be an outer path, so if its orientation is negative, + // then flag the whole group is 'reversed' (will negate delta etc.) + // as this is much more efficient than reversing every path. + is_reversed = (lowest_path_idx.has_value()) && Area(paths_in[lowest_path_idx.value()]) < 0; + } + else + { + lowest_path_idx = std::nullopt; + is_reversed = false; + } +} + +//------------------------------------------------------------------------------ +// ClipperOffset methods +//------------------------------------------------------------------------------ + +void ClipperOffset::AddPath(const Path64& path, JoinType jt_, EndType et_) +{ + groups_.emplace_back(Paths64(1, path), jt_, et_); +} + +void ClipperOffset::AddPaths(const Paths64 &paths, JoinType jt_, EndType et_) +{ + if (paths.size() == 0) return; + groups_.emplace_back(paths, jt_, et_); +} + +void ClipperOffset::BuildNormals(const Path64& path) +{ + norms.clear(); + norms.reserve(path.size()); + if (path.size() == 0) return; + Path64::const_iterator path_iter, path_stop_iter = --path.cend(); + for (path_iter = path.cbegin(); path_iter != path_stop_iter; ++path_iter) + norms.emplace_back(GetUnitNormal(*path_iter,*(path_iter +1))); + norms.emplace_back(GetUnitNormal(*path_stop_iter, *(path.cbegin()))); +} + +void ClipperOffset::DoBevel(const Path64& path, size_t j, size_t k) +{ + PointD pt1, pt2; + if (j == k) + { + double abs_delta = std::abs(group_delta_); +#ifdef USINGZ + pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y, path[j].z); + pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y, path[j].z); +#else + pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y); + pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y); +#endif + } + else + { +#ifdef USINGZ + pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y, path[j].z); + pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y, path[j].z); +#else + pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y); + pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y); +#endif + } + path_out.emplace_back(pt1); + path_out.emplace_back(pt2); +} + +void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k) +{ + PointD vec; + if (j == k) + vec = PointD(norms[j].y, -norms[j].x); + else + vec = GetAvgUnitVector( + PointD(-norms[k].y, norms[k].x), + PointD(norms[j].y, -norms[j].x)); + + double abs_delta = std::abs(group_delta_); + + // now offset the original vertex delta units along unit vector + PointD ptQ = PointD(path[j]); + ptQ = TranslatePoint(ptQ, abs_delta * vec.x, abs_delta * vec.y); + // get perpendicular vertices + PointD pt1 = TranslatePoint(ptQ, group_delta_ * vec.y, group_delta_ * -vec.x); + PointD pt2 = TranslatePoint(ptQ, group_delta_ * -vec.y, group_delta_ * vec.x); + // get 2 vertices along one edge offset + PointD pt3 = GetPerpendicD(path[k], norms[k], group_delta_); + if (j == k) + { + PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_); + PointD pt = ptQ; + GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt); + //get the second intersect point through reflecion + path_out.emplace_back(ReflectPoint(pt, ptQ)); + path_out.emplace_back(pt); + } + else + { + PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_); + PointD pt = ptQ; + GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt); + path_out.emplace_back(pt); + //get the second intersect point through reflecion + path_out.emplace_back(ReflectPoint(pt, ptQ)); + } +} + +void ClipperOffset::DoMiter(const Path64& path, size_t j, size_t k, double cos_a) +{ + double q = group_delta_ / (cos_a + 1); +#ifdef USINGZ + path_out.emplace_back( + path[j].x + (norms[k].x + norms[j].x) * q, + path[j].y + (norms[k].y + norms[j].y) * q, + path[j].z); +#else + path_out.emplace_back( + path[j].x + (norms[k].x + norms[j].x) * q, + path[j].y + (norms[k].y + norms[j].y) * q); +#endif +} + +void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle) +{ + if (deltaCallback64_) { + // when deltaCallback64_ is assigned, group_delta_ won't be constant, + // so we'll need to do the following calculations for *every* vertex. + double abs_delta = std::fabs(group_delta_); + double arcTol = (arc_tolerance_ > floating_point_tolerance ? + std::min(abs_delta, arc_tolerance_) : abs_delta * arc_const); + double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI); + step_sin_ = std::sin(2 * PI / steps_per_360); + step_cos_ = std::cos(2 * PI / steps_per_360); + if (group_delta_ < 0.0) step_sin_ = -step_sin_; + steps_per_rad_ = steps_per_360 / (2 * PI); + } + + Point64 pt = path[j]; + PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_); + + if (j == k) offsetVec.Negate(); +#ifdef USINGZ + path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z); +#else + path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y); +#endif + int steps = static_cast(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456 + for (int i = 1; i < steps; ++i) // ie 1 less than steps + { + offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y, + offsetVec.x * step_sin_ + offsetVec.y * step_cos_); +#ifdef USINGZ + path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z); +#else + path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y); +#endif + } + path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_)); +} + +void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size_t k) +{ + // Let A = change in angle where edges join + // A == 0: ie no change in angle (flat join) + // A == PI: edges 'spike' + // sin(A) < 0: right turning + // cos(A) < 0: change in angle is more than 90 degree + + if (path[j] == path[k]) return; + + double sin_a = CrossProduct(norms[j], norms[k]); + double cos_a = DotProduct(norms[j], norms[k]); + if (sin_a > 1.0) sin_a = 1.0; + else if (sin_a < -1.0) sin_a = -1.0; + + if (deltaCallback64_) { + group_delta_ = deltaCallback64_(path, norms, j, k); + if (group.is_reversed) group_delta_ = -group_delta_; + } + if (std::fabs(group_delta_) <= floating_point_tolerance) + { + path_out.emplace_back(path[j]); + return; + } + + if (cos_a > -0.999 && (sin_a * group_delta_ < 0)) // test for concavity first (#593) + { + // is concave + // by far the simplest way to construct concave joins, especially those joining very + // short segments, is to insert 3 points that produce negative regions. These regions + // will be removed later by the finishing union operation. This is also the best way + // to ensure that path reversals (ie over-shrunk paths) are removed. +#ifdef USINGZ + path_out.emplace_back(GetPerpendic(path[j], norms[k], group_delta_), path[j].z); + path_out.emplace_back(path[j]); // (#405, #873, #916) + path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_), path[j].z); +#else + path_out.emplace_back(GetPerpendic(path[j], norms[k], group_delta_)); + path_out.emplace_back(path[j]); // (#405, #873, #916) + path_out.emplace_back(GetPerpendic(path[j], norms[j], group_delta_)); +#endif + } + else if (cos_a > 0.999 && join_type_ != JoinType::Round) + { + // almost straight - less than 2.5 degree (#424, #482, #526 & #724) + DoMiter(path, j, k, cos_a); + } + else if (join_type_ == JoinType::Miter) + { + // miter unless the angle is sufficiently acute to exceed ML + if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a); + else DoSquare(path, j, k); + } + else if (join_type_ == JoinType::Round) + DoRound(path, j, k, std::atan2(sin_a, cos_a)); + else if ( join_type_ == JoinType::Bevel) + DoBevel(path, j, k); + else + DoSquare(path, j, k); +} + +void ClipperOffset::OffsetPolygon(Group& group, const Path64& path) +{ + path_out.clear(); + for (Path64::size_type j = 0, k = path.size() - 1; j < path.size(); k = j, ++j) + OffsetPoint(group, path, j, k); + solution->emplace_back(path_out); +} + +void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path) +{ + OffsetPolygon(group, path); + Path64 reverse_path(path); + std::reverse(reverse_path.begin(), reverse_path.end()); + + //rebuild normals + std::reverse(norms.begin(), norms.end()); + norms.emplace_back(norms[0]); + norms.erase(norms.begin()); + NegatePath(norms); + + OffsetPolygon(group, reverse_path); +} + +void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path) +{ + // do the line start cap + if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0); + + if (std::fabs(group_delta_) <= floating_point_tolerance) + path_out.emplace_back(path[0]); + else + { + switch (end_type_) + { + case EndType::Butt: + DoBevel(path, 0, 0); + break; + case EndType::Round: + DoRound(path, 0, 0, PI); + break; + default: + DoSquare(path, 0, 0); + break; + } + } + + size_t highI = path.size() - 1; + // offset the left side going forward + for (Path64::size_type j = 1, k = 0; j < highI; k = j, ++j) + OffsetPoint(group, path, j, k); + + // reverse normals + for (size_t i = highI; i > 0; --i) + norms[i] = PointD(-norms[i - 1].x, -norms[i - 1].y); + norms[0] = norms[highI]; + + // do the line end cap + if (deltaCallback64_) + group_delta_ = deltaCallback64_(path, norms, highI, highI); + + if (std::fabs(group_delta_) <= floating_point_tolerance) + path_out.emplace_back(path[highI]); + else + { + switch (end_type_) + { + case EndType::Butt: + DoBevel(path, highI, highI); + break; + case EndType::Round: + DoRound(path, highI, highI, PI); + break; + default: + DoSquare(path, highI, highI); + break; + } + } + + for (size_t j = highI -1, k = highI; j > 0; k = j, --j) + OffsetPoint(group, path, j, k); + solution->emplace_back(path_out); +} + +void ClipperOffset::DoGroupOffset(Group& group) +{ + if (group.end_type == EndType::Polygon) + { + // a straight path (2 points) can now also be 'polygon' offset + // where the ends will be treated as (180 deg.) joins + if (!group.lowest_path_idx.has_value()) delta_ = std::abs(delta_); + group_delta_ = (group.is_reversed) ? -delta_ : delta_; + } + else + group_delta_ = std::abs(delta_);// *0.5; + + double abs_delta = std::fabs(group_delta_); + join_type_ = group.join_type; + end_type_ = group.end_type; + + if (group.join_type == JoinType::Round || group.end_type == EndType::Round) + { + // calculate the number of steps required to approximate a circle + // (see https://www.angusj.com/clipper2/Docs/Trigonometry.htm) + // arcTol - when arc_tolerance_ is undefined (0) then curve imprecision + // will be relative to the size of the offset (delta). Obviously very + //large offsets will almost always require much less precision. + double arcTol = (arc_tolerance_ > floating_point_tolerance) ? + std::min(abs_delta, arc_tolerance_) : abs_delta * arc_const; + + double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI); + step_sin_ = std::sin(2 * PI / steps_per_360); + step_cos_ = std::cos(2 * PI / steps_per_360); + if (group_delta_ < 0.0) step_sin_ = -step_sin_; + steps_per_rad_ = steps_per_360 / (2 * PI); + } + + //double min_area = PI * Sqr(group_delta_); + Paths64::const_iterator path_in_it = group.paths_in.cbegin(); + for ( ; path_in_it != group.paths_in.cend(); ++path_in_it) + { + Path64::size_type pathLen = path_in_it->size(); + path_out.clear(); + + if (pathLen == 1) // single point + { + if (deltaCallback64_) + { + group_delta_ = deltaCallback64_(*path_in_it, norms, 0, 0); + if (group.is_reversed) group_delta_ = -group_delta_; + abs_delta = std::fabs(group_delta_); + } + + if (group_delta_ < 1) continue; + const Point64& pt = (*path_in_it)[0]; + //single vertex so build a circle or square ... + if (group.join_type == JoinType::Round) + { + double radius = abs_delta; + size_t steps = steps_per_rad_ > 0 ? static_cast(std::ceil(steps_per_rad_ * 2 * PI)) : 0; //#617 + path_out = Ellipse(pt, radius, radius, steps); +#ifdef USINGZ + for (auto& p : path_out) p.z = pt.z; +#endif + } + else + { + int d = (int)std::ceil(abs_delta); + Rect64 r = Rect64(pt.x - d, pt.y - d, pt.x + d, pt.y + d); + path_out = r.AsPath(); +#ifdef USINGZ + for (auto& p : path_out) p.z = pt.z; +#endif + } + + solution->emplace_back(path_out); + continue; + } // end of offsetting a single point + + if ((pathLen == 2) && (group.end_type == EndType::Joined)) + end_type_ = (group.join_type == JoinType::Round) ? + EndType::Round : + EndType::Square; + + BuildNormals(*path_in_it); + if (end_type_ == EndType::Polygon) OffsetPolygon(group, *path_in_it); + else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, *path_in_it); + else OffsetOpenPath(group, *path_in_it); + } +} + +#ifdef USINGZ +void ClipperOffset::ZCB(const Point64& bot1, const Point64& top1, + const Point64& bot2, const Point64& top2, Point64& ip) +{ + if (bot1.z && ((bot1.z == bot2.z) || (bot1.z == top2.z))) ip.z = bot1.z; + else if (bot2.z && (bot2.z == top1.z)) ip.z = bot2.z; + else if (top1.z && (top1.z == top2.z)) ip.z = top1.z; + else if (zCallback64_) zCallback64_(bot1, top1, bot2, top2, ip); +} +#endif + +size_t ClipperOffset::CalcSolutionCapacity() +{ + size_t result = 0; + for (const Group& g : groups_) + result += (g.end_type == EndType::Joined) ? g.paths_in.size() * 2 : g.paths_in.size(); + return result; +} + +bool ClipperOffset::CheckReverseOrientation() +{ + // nb: this assumes there's consistency in orientation between groups + bool is_reversed_orientation = false; + for (const Group& g : groups_) + if (g.end_type == EndType::Polygon) + { + is_reversed_orientation = g.is_reversed; + break; + } + return is_reversed_orientation; +} + +void ClipperOffset::ExecuteInternal(double delta) +{ + error_code_ = 0; + if (groups_.size() == 0) return; + solution->reserve(CalcSolutionCapacity()); + + if (std::abs(delta) < 0.5) // ie: offset is insignificant + { + Paths64::size_type sol_size = 0; + for (const Group& group : groups_) sol_size += group.paths_in.size(); + solution->reserve(sol_size); + for (const Group& group : groups_) + copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(*solution)); + } + else + { + + temp_lim_ = (miter_limit_ <= 1) ? + 2.0 : + 2.0 / (miter_limit_ * miter_limit_); + + delta_ = delta; + std::vector::iterator git; + for (git = groups_.begin(); git != groups_.end(); ++git) + { + DoGroupOffset(*git); + if (!error_code_) continue; // all OK + solution->clear(); + } + } + + if (!solution->size()) return; + + bool paths_reversed = CheckReverseOrientation(); + //clean up self-intersections ... + Clipper64 c; + c.PreserveCollinear(false); + //the solution should retain the orientation of the input + c.ReverseSolution(reverse_solution_ != paths_reversed); +#ifdef USINGZ + auto fp = std::bind(&ClipperOffset::ZCB, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, + std::placeholders::_4, std::placeholders::_5); + c.SetZCallback(fp); +#endif + c.AddSubject(*solution); + if (solution_tree) + { + if (paths_reversed) + c.Execute(ClipType::Union, FillRule::Negative, *solution_tree); + else + c.Execute(ClipType::Union, FillRule::Positive, *solution_tree); + } + else + { + if (paths_reversed) + c.Execute(ClipType::Union, FillRule::Negative, *solution); + else + c.Execute(ClipType::Union, FillRule::Positive, *solution); + } +} + +void ClipperOffset::Execute(double delta, Paths64& paths64) +{ + paths64.clear(); + solution = &paths64; + solution_tree = nullptr; + ExecuteInternal(delta); +} + + +void ClipperOffset::Execute(double delta, PolyTree64& polytree) +{ + polytree.Clear(); + solution_tree = &polytree; + solution = new Paths64(); + ExecuteInternal(delta); + delete solution; + solution = nullptr; +} + +void ClipperOffset::Execute(DeltaCallback64 delta_cb, Paths64& paths) +{ + deltaCallback64_ = delta_cb; + Execute(1.0, paths); +} + +} // namespace diff --git a/src/clipper2/Clipper2Lib/src/clipper.rectclip.cpp b/src/clipper2/Clipper2Lib/src/clipper.rectclip.cpp new file mode 100644 index 0000000000..a4d83ad137 --- /dev/null +++ b/src/clipper2/Clipper2Lib/src/clipper.rectclip.cpp @@ -0,0 +1,1031 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 5 July 2024 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2024 * +* Purpose : FAST rectangular clipping * +* License : https://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#include "clipper2/clipper.h" +#include "clipper2/clipper.rectclip.h" + +#ifdef USINGZ +namespace Clipper2Lib_Z { +#else +namespace Clipper2Lib { +#endif + + //------------------------------------------------------------------------------ + // Miscellaneous methods + //------------------------------------------------------------------------------ + + inline bool Path1ContainsPath2(const Path64& path1, const Path64& path2) + { + int io_count = 0; + // precondition: no (significant) overlap + for (const Point64& pt : path2) + { + PointInPolygonResult pip = PointInPolygon(pt, path1); + switch (pip) + { + case PointInPolygonResult::IsOutside: ++io_count; break; + case PointInPolygonResult::IsInside: --io_count; break; + default: continue; + } + if (std::abs(io_count) > 1) break; + } + return io_count <= 0; + } + + inline bool GetLocation(const Rect64& rec, + const Point64& pt, Location& loc) + { + if (pt.x == rec.left && pt.y >= rec.top && pt.y <= rec.bottom) + { + loc = Location::Left; + return false; + } + else if (pt.x == rec.right && pt.y >= rec.top && pt.y <= rec.bottom) + { + loc = Location::Right; + return false; + } + else if (pt.y == rec.top && pt.x >= rec.left && pt.x <= rec.right) + { + loc = Location::Top; + return false; + } + else if (pt.y == rec.bottom && pt.x >= rec.left && pt.x <= rec.right) + { + loc = Location::Bottom; + return false; + } + else if (pt.x < rec.left) loc = Location::Left; + else if (pt.x > rec.right) loc = Location::Right; + else if (pt.y < rec.top) loc = Location::Top; + else if (pt.y > rec.bottom) loc = Location::Bottom; + else loc = Location::Inside; + return true; + } + + inline bool IsHorizontal(const Point64& pt1, const Point64& pt2) + { + return pt1.y == pt2.y; + } + + bool GetSegmentIntersection(const Point64& p1, + const Point64& p2, const Point64& p3, const Point64& p4, Point64& ip) + { + double res1 = CrossProduct(p1, p3, p4); + double res2 = CrossProduct(p2, p3, p4); + if (res1 == 0) + { + ip = p1; + if (res2 == 0) return false; // segments are collinear + else if (p1 == p3 || p1 == p4) return true; + //else if (p2 == p3 || p2 == p4) { ip = p2; return true; } + else if (IsHorizontal(p3, p4)) return ((p1.x > p3.x) == (p1.x < p4.x)); + else return ((p1.y > p3.y) == (p1.y < p4.y)); + } + else if (res2 == 0) + { + ip = p2; + if (p2 == p3 || p2 == p4) return true; + else if (IsHorizontal(p3, p4)) return ((p2.x > p3.x) == (p2.x < p4.x)); + else return ((p2.y > p3.y) == (p2.y < p4.y)); + } + if ((res1 > 0) == (res2 > 0)) return false; + + double res3 = CrossProduct(p3, p1, p2); + double res4 = CrossProduct(p4, p1, p2); + if (res3 == 0) + { + ip = p3; + if (p3 == p1 || p3 == p2) return true; + else if (IsHorizontal(p1, p2)) return ((p3.x > p1.x) == (p3.x < p2.x)); + else return ((p3.y > p1.y) == (p3.y < p2.y)); + } + else if (res4 == 0) + { + ip = p4; + if (p4 == p1 || p4 == p2) return true; + else if (IsHorizontal(p1, p2)) return ((p4.x > p1.x) == (p4.x < p2.x)); + else return ((p4.y > p1.y) == (p4.y < p2.y)); + } + if ((res3 > 0) == (res4 > 0)) return false; + + // segments must intersect to get here + return GetSegmentIntersectPt(p1, p2, p3, p4, ip); + } + + inline bool GetIntersection(const Path64& rectPath, + const Point64& p, const Point64& p2, Location& loc, Point64& ip) + { + // gets the intersection closest to 'p' + // when Result = false, loc will remain unchanged + switch (loc) + { + case Location::Left: + if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) return true; + else if ((p.y < rectPath[0].y) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) + { + loc = Location::Top; + return true; + } + else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) + { + loc = Location::Bottom; + return true; + } + else return false; + + case Location::Top: + if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) return true; + else if ((p.x < rectPath[0].x) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) + { + loc = Location::Left; + return true; + } + else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) + { + loc = Location::Right; + return true; + } + else return false; + + case Location::Right: + if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) return true; + else if ((p.y < rectPath[1].y) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) + { + loc = Location::Top; + return true; + } + else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) + { + loc = Location::Bottom; + return true; + } + else return false; + + case Location::Bottom: + if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) return true; + else if ((p.x < rectPath[3].x) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) + { + loc = Location::Left; + return true; + } + else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) + { + loc = Location::Right; + return true; + } + else return false; + + default: // loc == rInside + if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) + { + loc = Location::Left; + return true; + } + else if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) + { + loc = Location::Top; + return true; + } + else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) + { + loc = Location::Right; + return true; + } + else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) + { + loc = Location::Bottom; + return true; + } + else return false; + } + } + + inline Location GetAdjacentLocation(Location loc, bool isClockwise) + { + int delta = (isClockwise) ? 1 : 3; + return static_cast((static_cast(loc) + delta) % 4); + } + + inline bool HeadingClockwise(Location prev, Location curr) + { + return (static_cast(prev) + 1) % 4 == static_cast(curr); + } + + inline bool AreOpposites(Location prev, Location curr) + { + return abs(static_cast(prev) - static_cast(curr)) == 2; + } + + inline bool IsClockwise(Location prev, Location curr, + const Point64& prev_pt, const Point64& curr_pt, const Point64& rect_mp) + { + if (AreOpposites(prev, curr)) + return CrossProduct(prev_pt, rect_mp, curr_pt) < 0; + else + return HeadingClockwise(prev, curr); + } + + inline OutPt2* UnlinkOp(OutPt2* op) + { + if (op->next == op) return nullptr; + op->prev->next = op->next; + op->next->prev = op->prev; + return op->next; + } + + inline OutPt2* UnlinkOpBack(OutPt2* op) + { + if (op->next == op) return nullptr; + op->prev->next = op->next; + op->next->prev = op->prev; + return op->prev; + } + + inline uint32_t GetEdgesForPt(const Point64& pt, const Rect64& rec) + { + uint32_t result = 0; + if (pt.x == rec.left) result = 1; + else if (pt.x == rec.right) result = 4; + if (pt.y == rec.top) result += 2; + else if (pt.y == rec.bottom) result += 8; + return result; + } + + inline bool IsHeadingClockwise(const Point64& pt1, const Point64& pt2, int edgeIdx) + { + switch (edgeIdx) + { + case 0: return pt2.y < pt1.y; + case 1: return pt2.x > pt1.x; + case 2: return pt2.y > pt1.y; + default: return pt2.x < pt1.x; + } + } + + inline bool HasHorzOverlap(const Point64& left1, const Point64& right1, + const Point64& left2, const Point64& right2) + { + return (left1.x < right2.x) && (right1.x > left2.x); + } + + inline bool HasVertOverlap(const Point64& top1, const Point64& bottom1, + const Point64& top2, const Point64& bottom2) + { + return (top1.y < bottom2.y) && (bottom1.y > top2.y); + } + + inline void AddToEdge(OutPt2List& edge, OutPt2* op) + { + if (op->edge) return; + op->edge = &edge; + edge.emplace_back(op); + } + + inline void UncoupleEdge(OutPt2* op) + { + if (!op->edge) return; + for (size_t i = 0; i < op->edge->size(); ++i) + { + OutPt2* op2 = (*op->edge)[i]; + if (op2 == op) + { + (*op->edge)[i] = nullptr; + break; + } + } + op->edge = nullptr; + } + + inline void SetNewOwner(OutPt2* op, size_t new_idx) + { + op->owner_idx = new_idx; + OutPt2* op2 = op->next; + while (op2 != op) + { + op2->owner_idx = new_idx; + op2 = op2->next; + } + } + + //---------------------------------------------------------------------------- + // RectClip64 + //---------------------------------------------------------------------------- + + OutPt2* RectClip64::Add(Point64 pt, bool start_new) + { + // this method is only called by InternalExecute. + // Later splitting & rejoining won't create additional op's, + // though they will change the (non-storage) results_ count. + size_t curr_idx = results_.size(); + OutPt2* result; + if (curr_idx == 0 || start_new) + { + result = &op_container_.emplace_back(OutPt2()); + result->pt = pt; + result->next = result; + result->prev = result; + results_.emplace_back(result); + } + else + { + --curr_idx; + OutPt2* prevOp = results_[curr_idx]; + if (prevOp->pt == pt) return prevOp; + result = &op_container_.emplace_back(OutPt2()); + result->owner_idx = curr_idx; + result->pt = pt; + result->next = prevOp->next; + prevOp->next->prev = result; + prevOp->next = result; + result->prev = prevOp; + results_[curr_idx] = result; + } + return result; + } + + void RectClip64::AddCorner(Location prev, Location curr) + { + if (HeadingClockwise(prev, curr)) + Add(rect_as_path_[static_cast(prev)]); + else + Add(rect_as_path_[static_cast(curr)]); + } + + void RectClip64::AddCorner(Location& loc, bool isClockwise) + { + if (isClockwise) + { + Add(rect_as_path_[static_cast(loc)]); + loc = GetAdjacentLocation(loc, true); + } + else + { + loc = GetAdjacentLocation(loc, false); + Add(rect_as_path_[static_cast(loc)]); + } + } + + void RectClip64::GetNextLocation(const Path64& path, + Location& loc, size_t& i, size_t highI) + { + switch (loc) + { + case Location::Left: + while (i <= highI && path[i].x <= rect_.left) ++i; + if (i > highI) break; + else if (path[i].x >= rect_.right) loc = Location::Right; + else if (path[i].y <= rect_.top) loc = Location::Top; + else if (path[i].y >= rect_.bottom) loc = Location::Bottom; + else loc = Location::Inside; + break; + + case Location::Top: + while (i <= highI && path[i].y <= rect_.top) ++i; + if (i > highI) break; + else if (path[i].y >= rect_.bottom) loc = Location::Bottom; + else if (path[i].x <= rect_.left) loc = Location::Left; + else if (path[i].x >= rect_.right) loc = Location::Right; + else loc = Location::Inside; + break; + + case Location::Right: + while (i <= highI && path[i].x >= rect_.right) ++i; + if (i > highI) break; + else if (path[i].x <= rect_.left) loc = Location::Left; + else if (path[i].y <= rect_.top) loc = Location::Top; + else if (path[i].y >= rect_.bottom) loc = Location::Bottom; + else loc = Location::Inside; + break; + + case Location::Bottom: + while (i <= highI && path[i].y >= rect_.bottom) ++i; + if (i > highI) break; + else if (path[i].y <= rect_.top) loc = Location::Top; + else if (path[i].x <= rect_.left) loc = Location::Left; + else if (path[i].x >= rect_.right) loc = Location::Right; + else loc = Location::Inside; + break; + + case Location::Inside: + while (i <= highI) + { + if (path[i].x < rect_.left) loc = Location::Left; + else if (path[i].x > rect_.right) loc = Location::Right; + else if (path[i].y > rect_.bottom) loc = Location::Bottom; + else if (path[i].y < rect_.top) loc = Location::Top; + else { Add(path[i]); ++i; continue; } + break; //inner loop + } + break; + } //switch + } + + bool StartLocsAreClockwise(const std::vector& startlocs) + { + int result = 0; + for (size_t i = 1; i < startlocs.size(); ++i) + { + int d = static_cast(startlocs[i]) - static_cast(startlocs[i - 1]); + switch (d) + { + case -1: result -= 1; break; + case 1: result += 1; break; + case -3: result += 1; break; + case 3: result -= 1; break; + } + } + return result > 0; + } + + void RectClip64::ExecuteInternal(const Path64& path) + { + if (path.size() < 1) + return; + + size_t highI = path.size() - 1; + Location prev = Location::Inside, loc; + Location crossing_loc = Location::Inside; + Location first_cross_ = Location::Inside; + if (!GetLocation(rect_, path[highI], loc)) + { + size_t i = highI; + while (i > 0 && !GetLocation(rect_, path[i - 1], prev)) + --i; + if (i == 0) + { + // all of path must be inside fRect + for (const auto& pt : path) Add(pt); + return; + } + if (prev == Location::Inside) loc = Location::Inside; + } + Location starting_loc = loc; + + /////////////////////////////////////////////////// + size_t i = 0; + while (i <= highI) + { + prev = loc; + Location crossing_prev = crossing_loc; + + GetNextLocation(path, loc, i, highI); + + if (i > highI) break; + Point64 ip, ip2; + Point64 prev_pt = (i) ? + path[static_cast(i - 1)] : + path[highI]; + + crossing_loc = loc; + if (!GetIntersection(rect_as_path_, + path[i], prev_pt, crossing_loc, ip)) + { + // ie remaining outside + if (crossing_prev == Location::Inside) + { + bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], rect_mp_); + do { + start_locs_.emplace_back(prev); + prev = GetAdjacentLocation(prev, isClockw); + } while (prev != loc); + crossing_loc = crossing_prev; // still not crossed + } + else if (prev != Location::Inside && prev != loc) + { + bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], rect_mp_); + do { + AddCorner(prev, isClockw); + } while (prev != loc); + } + ++i; + continue; + } + + //////////////////////////////////////////////////// + // we must be crossing the rect boundary to get here + //////////////////////////////////////////////////// + + if (loc == Location::Inside) // path must be entering rect + { + if (first_cross_ == Location::Inside) + { + first_cross_ = crossing_loc; + start_locs_.emplace_back(prev); + } + else if (prev != crossing_loc) + { + bool isClockw = IsClockwise(prev, crossing_loc, prev_pt, path[i], rect_mp_); + do { + AddCorner(prev, isClockw); + } while (prev != crossing_loc); + } + } + else if (prev != Location::Inside) + { + // passing right through rect. 'ip' here will be the second + // intersect pt but we'll also need the first intersect pt (ip2) + loc = prev; + GetIntersection(rect_as_path_, prev_pt, path[i], loc, ip2); + if (crossing_prev != Location::Inside && crossing_prev != loc) //579 + AddCorner(crossing_prev, loc); + + if (first_cross_ == Location::Inside) + { + first_cross_ = loc; + start_locs_.emplace_back(prev); + } + + loc = crossing_loc; + Add(ip2); + if (ip == ip2) + { + // it's very likely that path[i] is on rect + GetLocation(rect_, path[i], loc); + AddCorner(crossing_loc, loc); + crossing_loc = loc; + continue; + } + } + else // path must be exiting rect + { + loc = crossing_loc; + if (first_cross_ == Location::Inside) + first_cross_ = crossing_loc; + } + + Add(ip); + + } //while i <= highI + /////////////////////////////////////////////////// + + if (first_cross_ == Location::Inside) + { + // path never intersects + if (starting_loc != Location::Inside) + { + // path is outside rect + // but being outside, it still may not contain rect + if (path_bounds_.Contains(rect_) && + Path1ContainsPath2(path, rect_as_path_)) + { + // yep, the path does fully contain rect + // so add rect to the solution + bool is_clockwise_path = StartLocsAreClockwise(start_locs_); + for (size_t j = 0; j < 4; ++j) + { + size_t k = is_clockwise_path ? j : 3 - j; // reverses result path + Add(rect_as_path_[k]); + // we may well need to do some splitting later, so + AddToEdge(edges_[k * 2], results_[0]); + } + } + } + } + else if (loc != Location::Inside && + (loc != first_cross_ || start_locs_.size() > 2)) + { + if (start_locs_.size() > 0) + { + prev = loc; + for (auto loc2 : start_locs_) + { + if (prev == loc2) continue; + AddCorner(prev, HeadingClockwise(prev, loc2)); + prev = loc2; + } + loc = prev; + } + if (loc != first_cross_) + AddCorner(loc, HeadingClockwise(loc, first_cross_)); + } + } + + void RectClip64::CheckEdges() + { + for (size_t i = 0; i < results_.size(); ++i) + { + OutPt2* op = results_[i]; + if (!op) continue; + OutPt2* op2 = op; + do + { + if (IsCollinear(op2->prev->pt, op2->pt, op2->next->pt)) + { + if (op2 == op) + { + op2 = UnlinkOpBack(op2); + if (!op2) break; + op = op2->prev; + } + else + { + op2 = UnlinkOpBack(op2); + if (!op2) break; + } + } + else + op2 = op2->next; + } while (op2 != op); + + if (!op2) + { + results_[i] = nullptr; + continue; + } + results_[i] = op; // safety first + + uint32_t edgeSet1 = GetEdgesForPt(op->prev->pt, rect_); + op2 = op; + do + { + uint32_t edgeSet2 = GetEdgesForPt(op2->pt, rect_); + if (edgeSet2 && !op2->edge) + { + uint32_t combinedSet = (edgeSet1 & edgeSet2); + for (int j = 0; j < 4; ++j) + { + if (combinedSet & (1 << j)) + { + if (IsHeadingClockwise(op2->prev->pt, op2->pt, j)) + AddToEdge(edges_[j * 2], op2); + else + AddToEdge(edges_[j * 2 + 1], op2); + } + } + } + edgeSet1 = edgeSet2; + op2 = op2->next; + } while (op2 != op); + } + } + + void RectClip64::TidyEdges(size_t idx, OutPt2List& cw, OutPt2List& ccw) + { + if (ccw.empty()) return; + bool isHorz = ((idx == 1) || (idx == 3)); + bool cwIsTowardLarger = ((idx == 1) || (idx == 2)); + size_t i = 0, j = 0; + OutPt2* p1, * p2, * p1a, * p2a, * op, * op2; + + while (i < cw.size()) + { + p1 = cw[i]; + if (!p1 || p1->next == p1->prev) + { + cw[i++] = nullptr; + j = 0; + continue; + } + + size_t jLim = ccw.size(); + while (j < jLim && + (!ccw[j] || ccw[j]->next == ccw[j]->prev)) ++j; + + if (j == jLim) + { + ++i; + j = 0; + continue; + } + + if (cwIsTowardLarger) + { + // p1 >>>> p1a; + // p2 <<<< p2a; + p1 = cw[i]->prev; + p1a = cw[i]; + p2 = ccw[j]; + p2a = ccw[j]->prev; + } + else + { + // p1 <<<< p1a; + // p2 >>>> p2a; + p1 = cw[i]; + p1a = cw[i]->prev; + p2 = ccw[j]->prev; + p2a = ccw[j]; + } + + if ((isHorz && !HasHorzOverlap(p1->pt, p1a->pt, p2->pt, p2a->pt)) || + (!isHorz && !HasVertOverlap(p1->pt, p1a->pt, p2->pt, p2a->pt))) + { + ++j; + continue; + } + + // to get here we're either splitting or rejoining + bool isRejoining = cw[i]->owner_idx != ccw[j]->owner_idx; + + if (isRejoining) + { + results_[p2->owner_idx] = nullptr; + SetNewOwner(p2, p1->owner_idx); + } + + // do the split or re-join + if (cwIsTowardLarger) + { + // p1 >> | >> p1a; + // p2 << | << p2a; + p1->next = p2; + p2->prev = p1; + p1a->prev = p2a; + p2a->next = p1a; + } + else + { + // p1 << | << p1a; + // p2 >> | >> p2a; + p1->prev = p2; + p2->next = p1; + p1a->next = p2a; + p2a->prev = p1a; + } + + if (!isRejoining) + { + size_t new_idx = results_.size(); + results_.emplace_back(p1a); + SetNewOwner(p1a, new_idx); + } + + if (cwIsTowardLarger) + { + op = p2; + op2 = p1a; + } + else + { + op = p1; + op2 = p2a; + } + results_[op->owner_idx] = op; + results_[op2->owner_idx] = op2; + + // and now lots of work to get ready for the next loop + + bool opIsLarger, op2IsLarger; + if (isHorz) // X + { + opIsLarger = op->pt.x > op->prev->pt.x; + op2IsLarger = op2->pt.x > op2->prev->pt.x; + } + else // Y + { + opIsLarger = op->pt.y > op->prev->pt.y; + op2IsLarger = op2->pt.y > op2->prev->pt.y; + } + + if ((op->next == op->prev) || + (op->pt == op->prev->pt)) + { + if (op2IsLarger == cwIsTowardLarger) + { + cw[i] = op2; + ccw[j++] = nullptr; + } + else + { + ccw[j] = op2; + cw[i++] = nullptr; + } + } + else if ((op2->next == op2->prev) || + (op2->pt == op2->prev->pt)) + { + if (opIsLarger == cwIsTowardLarger) + { + cw[i] = op; + ccw[j++] = nullptr; + } + else + { + ccw[j] = op; + cw[i++] = nullptr; + } + } + else if (opIsLarger == op2IsLarger) + { + if (opIsLarger == cwIsTowardLarger) + { + cw[i] = op; + UncoupleEdge(op2); + AddToEdge(cw, op2); + ccw[j++] = nullptr; + } + else + { + cw[i++] = nullptr; + ccw[j] = op2; + UncoupleEdge(op); + AddToEdge(ccw, op); + j = 0; + } + } + else + { + if (opIsLarger == cwIsTowardLarger) + cw[i] = op; + else + ccw[j] = op; + if (op2IsLarger == cwIsTowardLarger) + cw[i] = op2; + else + ccw[j] = op2; + } + } + } + + Path64 RectClip64::GetPath(OutPt2*& op) + { + if (!op || op->next == op->prev) return Path64(); + + OutPt2* op2 = op->next; + while (op2 && op2 != op) + { + if (IsCollinear(op2->prev->pt, + op2->pt, op2->next->pt)) + { + op = op2->prev; + op2 = UnlinkOp(op2); + } + else + op2 = op2->next; + } + op = op2; // needed for op cleanup + if (!op2) return Path64(); + + Path64 result; + result.emplace_back(op->pt); + op2 = op->next; + while (op2 != op) + { + result.emplace_back(op2->pt); + op2 = op2->next; + } + return result; + } + + Paths64 RectClip64::Execute(const Paths64& paths) + { + Paths64 result; + if (rect_.IsEmpty()) return result; + + for (const Path64& path : paths) + { + if (path.size() < 3) continue; + path_bounds_ = GetBounds(path); + if (!rect_.Intersects(path_bounds_)) + continue; // the path must be completely outside rect_ + else if (rect_.Contains(path_bounds_)) + { + // the path must be completely inside rect_ + result.emplace_back(path); + continue; + } + + ExecuteInternal(path); + CheckEdges(); + for (size_t i = 0; i < 4; ++i) + TidyEdges(i, edges_[i * 2], edges_[i * 2 + 1]); + + for (OutPt2*& op : results_) + { + Path64 tmp = GetPath(op); + if (!tmp.empty()) + result.emplace_back(std::move(tmp)); + } + + //clean up after every loop + op_container_ = std::deque(); + results_.clear(); + for (OutPt2List &edge : edges_) edge.clear(); + start_locs_.clear(); + } + return result; + } + + //------------------------------------------------------------------------------ + // RectClipLines64 + //------------------------------------------------------------------------------ + + Paths64 RectClipLines64::Execute(const Paths64& paths) + { + Paths64 result; + if (rect_.IsEmpty()) return result; + + for (const auto& path : paths) + { + Rect64 pathrec = GetBounds(path); + if (!rect_.Intersects(pathrec)) continue; + + ExecuteInternal(path); + + for (OutPt2*& op : results_) + { + Path64 tmp = GetPath(op); + if (!tmp.empty()) + result.emplace_back(std::move(tmp)); + } + results_.clear(); + + op_container_ = std::deque(); + start_locs_.clear(); + } + return result; + } + + void RectClipLines64::ExecuteInternal(const Path64& path) + { + if (rect_.IsEmpty() || path.size() < 2) return; + + results_.clear(); + op_container_ = std::deque(); + start_locs_.clear(); + + size_t i = 1, highI = path.size() - 1; + + Location prev = Location::Inside, loc; + Location crossing_loc; + if (!GetLocation(rect_, path[0], loc)) + { + while (i <= highI && !GetLocation(rect_, path[i], prev)) ++i; + if (i > highI) + { + // all of path must be inside fRect + for (const auto& pt : path) Add(pt); + return; + } + if (prev == Location::Inside) loc = Location::Inside; + i = 1; + } + if (loc == Location::Inside) Add(path[0]); + + /////////////////////////////////////////////////// + while (i <= highI) + { + prev = loc; + GetNextLocation(path, loc, i, highI); + if (i > highI) break; + Point64 ip, ip2; + Point64 prev_pt = path[static_cast(i - 1)]; + + crossing_loc = loc; + if (!GetIntersection(rect_as_path_, + path[i], prev_pt, crossing_loc, ip)) + { + // ie remaining outside + ++i; + continue; + } + + //////////////////////////////////////////////////// + // we must be crossing the rect boundary to get here + //////////////////////////////////////////////////// + + if (loc == Location::Inside) // path must be entering rect + { + Add(ip, true); + } + else if (prev != Location::Inside) + { + // passing right through rect. 'ip' here will be the second + // intersect pt but we'll also need the first intersect pt (ip2) + crossing_loc = prev; + GetIntersection(rect_as_path_, + prev_pt, path[i], crossing_loc, ip2); + Add(ip2, true); + Add(ip); + } + else // path must be exiting rect + { + Add(ip); + } + } //while i <= highI + /////////////////////////////////////////////////// + } + + Path64 RectClipLines64::GetPath(OutPt2*& op) + { + Path64 result; + if (!op || op == op->next) return result; + op = op->next; // starting at path beginning + result.emplace_back(op->pt); + OutPt2 *op2 = op->next; + while (op2 != op) + { + result.emplace_back(op2->pt); + op2 = op2->next; + } + return result; + } + +} // namespace diff --git a/src/clipper2/Clipper2Lib/src/clipper2_z.cpp b/src/clipper2/Clipper2Lib/src/clipper2_z.cpp new file mode 100644 index 0000000000..f5c7c6f4fd --- /dev/null +++ b/src/clipper2/Clipper2Lib/src/clipper2_z.cpp @@ -0,0 +1,8 @@ +// Hackish wrapper around the ClipperLib library to compile the Clipper library with the Z support. +// Enable the Z coordinate support. +#define USINGZ + +// and let it compile +#include "clipper.engine.cpp" +#include "clipper.offset.cpp" +#include "clipper.rectclip.cpp" diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 44e6d0878d..657c8dc15f 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -92,6 +92,8 @@ set(lisbslic3r_sources clipper.hpp ClipperUtils.cpp ClipperUtils.hpp + Clipper2Utils.cpp + Clipper2Utils.hpp ClipperZUtils.hpp Color.cpp Color.hpp @@ -578,6 +580,7 @@ target_link_libraries(libslic3r boost_libs cereal::cereal clipper + Clipper2 eigen glu-libtess JPEG::JPEG diff --git a/src/libslic3r/Clipper2Utils.cpp b/src/libslic3r/Clipper2Utils.cpp new file mode 100644 index 0000000000..9f1f678a9c --- /dev/null +++ b/src/libslic3r/Clipper2Utils.cpp @@ -0,0 +1,211 @@ +#include "Clipper2Utils.hpp" +#include "libslic3r.h" +#include "clipper2/clipper.h" + +namespace Slic3r { + +//BBS: FIXME +Slic3r::Polylines Paths64_to_polylines(const Clipper2Lib::Paths64& in) +{ + Slic3r::Polylines out; + out.reserve(in.size()); + for (const Clipper2Lib::Path64& path64 : in) { + Slic3r::Points points; + points.reserve(path64.size()); + for (const Clipper2Lib::Point64& point64 : path64) + points.emplace_back(std::move(Slic3r::Point(point64.x, point64.y))); + out.emplace_back(std::move(Slic3r::Polyline(points))); + } + return out; +} + +//BBS: FIXME +template +Clipper2Lib::Paths64 Slic3rPoints_to_Paths64(const Container& in) +{ + Clipper2Lib::Paths64 out; + out.reserve(in.size()); + for (const auto& item : in) { + Clipper2Lib::Path64 path; + path.reserve(item.size()); + for (const Slic3r::Point& point : item.points) + path.emplace_back(std::move(Clipper2Lib::Point64(point.x(), point.y()))); + out.emplace_back(std::move(path)); + } + return out; +} + +Points Path64ToPoints(const Clipper2Lib::Path64& path64) +{ + Points points; + points.reserve(path64.size()); + for (const Clipper2Lib::Point64 &point64 : path64) points.emplace_back(std::move(Slic3r::Point(point64.x, point64.y))); + return points; +} + +static ExPolygons PolyTreeToExPolygons(Clipper2Lib::PolyTree64 &&polytree) +{ + struct Inner + { + static void PolyTreeToExPolygonsRecursive(Clipper2Lib::PolyTree64 &&polynode, ExPolygons *expolygons) + { + size_t cnt = expolygons->size(); + expolygons->resize(cnt + 1); + (*expolygons)[cnt].contour.points = Path64ToPoints(polynode.Polygon()); + + (*expolygons)[cnt].holes.resize(polynode.Count()); + for (int i = 0; i < polynode.Count(); ++i) { + (*expolygons)[cnt].holes[i].points = Path64ToPoints(polynode[i]->Polygon()); + // Add outer polygons contained by (nested within) holes. + for (int j = 0; j < polynode[i]->Count(); ++j) PolyTreeToExPolygonsRecursive(std::move(*polynode[i]->Child(j)), expolygons); + } + } + + static size_t PolyTreeCountExPolygons(const Clipper2Lib::PolyPath64& polynode) + { + size_t cnt = 1; + for (size_t i = 0; i < polynode.Count(); ++i) { + for (size_t j = 0; j < polynode.Child(i)->Count(); ++j) cnt += PolyTreeCountExPolygons(*polynode.Child(i)->Child(j)); + } + return cnt; + } + }; + + ExPolygons retval; + size_t cnt = 0; + for (int i = 0; i < polytree.Count(); ++i) cnt += Inner::PolyTreeCountExPolygons(*polytree[i]); + retval.reserve(cnt); + for (int i = 0; i < polytree.Count(); ++i) Inner::PolyTreeToExPolygonsRecursive(std::move(*polytree[i]), &retval); + return retval; +} + +void SimplifyPolyTree(const Clipper2Lib::PolyPath64 &polytree, double epsilon, Clipper2Lib::PolyPath64 &result) +{ + for (const auto &child : polytree) { + Clipper2Lib::PolyPath64 *newchild = result.AddChild(Clipper2Lib::SimplifyPath(child->Polygon(), epsilon)); + SimplifyPolyTree(*child, epsilon, *newchild); + } +} + +Clipper2Lib::Paths64 Slic3rPolygons_to_Paths64(const Polygons &in) +{ + Clipper2Lib::Paths64 out; + out.reserve(in.size()); + for (const Polygon &poly : in) { + Clipper2Lib::Path64 path; + path.reserve(poly.points.size()); + for (const Slic3r::Point &point : poly.points) path.emplace_back(std::move(Clipper2Lib::Point64(point.x(), point.y()))); + out.emplace_back(std::move(path)); + } + return out; +} + +Clipper2Lib::Paths64 Slic3rExPolygons_to_Paths64(const ExPolygons& in) +{ + Clipper2Lib::Paths64 out; + out.reserve(in.size()); + for (const ExPolygon& expolygon : in) { + for (size_t i = 0; i < expolygon.num_contours(); i++) { + const auto &poly = expolygon.contour_or_hole(i); + Clipper2Lib::Path64 path; + path.reserve(poly.points.size()); + for (const Slic3r::Point &point : poly.points) path.emplace_back(std::move(Clipper2Lib::Point64(point.x(), point.y()))); + out.emplace_back(std::move(path)); + } + } + return out; +} + +Polylines _clipper2_pl_open(Clipper2Lib::ClipType clipType, const Slic3r::Polylines& subject, const Slic3r::Polygons& clip) +{ + Clipper2Lib::Clipper64 c; + c.AddOpenSubject(Slic3rPoints_to_Paths64(subject)); + c.AddClip(Slic3rPoints_to_Paths64(clip)); + + Clipper2Lib::ClipType ct = clipType; + Clipper2Lib::FillRule fr = Clipper2Lib::FillRule::NonZero; + Clipper2Lib::Paths64 solution, solution_open; + c.Execute(ct, fr, solution, solution_open); + + Slic3r::Polylines out; + out.reserve(solution.size() + solution_open.size()); + polylines_append(out, std::move(Paths64_to_polylines(solution))); + polylines_append(out, std::move(Paths64_to_polylines(solution_open))); + + return out; +} + +Slic3r::Polylines intersection_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip) + { return _clipper2_pl_open(Clipper2Lib::ClipType::Intersection, subject, clip); } +Slic3r::Polylines diff_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip) + { return _clipper2_pl_open(Clipper2Lib::ClipType::Difference, subject, clip); } + +ExPolygons union_ex_2(const Polygons& polygons) +{ + Clipper2Lib::Clipper64 c; + c.AddSubject(Slic3rPolygons_to_Paths64(polygons)); + + Clipper2Lib::ClipType ct = Clipper2Lib::ClipType::Union; + Clipper2Lib::FillRule fr = Clipper2Lib::FillRule::NonZero; + Clipper2Lib::PolyTree64 solution; + c.Execute(ct, fr, solution); + + ExPolygons results = PolyTreeToExPolygons(std::move(solution)); + + return results; +} + +ExPolygons union_ex_2(const ExPolygons &expolygons) +{ + Clipper2Lib::Clipper64 c; + c.AddSubject(Slic3rExPolygons_to_Paths64(expolygons)); + + Clipper2Lib::ClipType ct = Clipper2Lib::ClipType::Union; + Clipper2Lib::FillRule fr = Clipper2Lib::FillRule::NonZero; + Clipper2Lib::PolyTree64 solution; + c.Execute(ct, fr, solution); + + ExPolygons results = PolyTreeToExPolygons(std::move(solution)); + + return results; +} + +// 对 ExPolygons 进行偏移 +ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta) +{ + Clipper2Lib::Paths64 subject = Slic3rExPolygons_to_Paths64(expolygons); + Clipper2Lib::ClipperOffset offsetter; + offsetter.AddPaths(subject, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); + Clipper2Lib::PolyPath64 polytree; + offsetter.Execute(delta, polytree); + ExPolygons results = PolyTreeToExPolygons(std::move(polytree)); + + return results; +} + +ExPolygons offset2_ex_2(const ExPolygons& expolygons, double delta1, double delta2) +{ + // 1st offset + Clipper2Lib::Paths64 subject = Slic3rExPolygons_to_Paths64(expolygons); + Clipper2Lib::ClipperOffset offsetter; + offsetter.AddPaths(subject, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); + Clipper2Lib::PolyPath64 polytree; + offsetter.Execute(delta1, polytree); + + // simplify the result + Clipper2Lib::PolyPath64 polytree2; + SimplifyPolyTree(polytree, SCALED_EPSILON, polytree2); + + // 2nd offset + offsetter.Clear(); + offsetter.AddPaths(Clipper2Lib::PolyTreeToPaths64(polytree2), Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); + polytree.Clear(); + offsetter.Execute(delta2, polytree); + + // convert back to expolygons + ExPolygons results = PolyTreeToExPolygons(std::move(polytree)); + + return results; +} + +} \ No newline at end of file diff --git a/src/libslic3r/Clipper2Utils.hpp b/src/libslic3r/Clipper2Utils.hpp new file mode 100644 index 0000000000..c5485ad1bd --- /dev/null +++ b/src/libslic3r/Clipper2Utils.hpp @@ -0,0 +1,18 @@ +#ifndef slic3r_Clipper2Utils_hpp_ +#define slic3r_Clipper2Utils_hpp_ + +#include "ExPolygon.hpp" +#include "Polygon.hpp" +#include "Polyline.hpp" + +namespace Slic3r { + +Slic3r::Polylines intersection_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip); +Slic3r::Polylines diff_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip); +ExPolygons union_ex_2(const Polygons &expolygons); +ExPolygons union_ex_2(const ExPolygons &expolygons); +ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta); +ExPolygons offset2_ex_2(const ExPolygons &expolygons, double delta1, double delta2); +} + +#endif \ No newline at end of file diff --git a/src/libslic3r/Clipper2ZUtils.hpp b/src/libslic3r/Clipper2ZUtils.hpp new file mode 100644 index 0000000000..b0891d1c63 --- /dev/null +++ b/src/libslic3r/Clipper2ZUtils.hpp @@ -0,0 +1,167 @@ +#ifndef slic3r_Clipper2ZUtils_hpp_ +#define slic3r_Clipper2ZUtils_hpp_ +#include +#include +#include + +#include +#include +namespace Slic3r { namespace Clipper2ZUtils { + +using ZPoint64 = Clipper2Lib_Z::Point64; +using ZPoints64 = Clipper2Lib_Z::Path64; +using ZPath64 = Clipper2Lib_Z::Path64; +using ZPaths64 = Clipper2Lib_Z::Paths64; + +inline bool zpoint64_lower(const ZPoint64 &l, const ZPoint64 &r) { + return l.x < r.x || (l.x == r.x && (l.y < r.y || (l.y == r.y && l.z < r.z))); +} + +// Convert a single path to zpath with a given Z coordinate. +// If Open, then duplicate the first point at the end. +template +inline ZPath64 to_zpath64(const Points &path, int64_t z) +{ + ZPath64 out; + if (!path.empty()) { + out.reserve(path.size() + (Open ? 1 : 0)); + for (const Point &p : path) out.emplace_back(p.x(), p.y(), z); + if (Open) out.emplace_back(out.front()); + } + return out; +} + +template +inline ZPath64 to_zpath64(const Clipper2Lib_Z::Path64 &path, int64_t z) +{ + ZPath64 out; + if (!path.empty()) { + out.reserve(path.size() + (Open ? 1 : 0)); + for (const Clipper2Lib_Z::Point64 &p : path) out.emplace_back(p.x, p.y, z); + if (Open) out.emplace_back(out.front()); + } + return out; +} + +// Convert multiple paths to zpaths with a given Z coordinate. +template +inline ZPaths64 to_zpaths64(const VecOfPoints &paths, int64_t z) +{ + ZPaths64 out; + out.reserve(paths.size()); + for (const Points &path : paths) out.emplace_back(to_zpath64(path, z)); + return out; +} + +template +inline ZPaths64 to_zpaths64(const Clipper2Lib_Z::Paths64 &paths, int64_t z) +{ + ZPaths64 out; + out.reserve(paths.size()); + for (const Clipper2Lib_Z::Path64 &path : paths) out.emplace_back(to_zpath64(path, z)); + return out; +} + +// Convert multiple expolygons into zpaths with Z specified by index +// offset by base_idx. +template +inline ZPaths64 expolygons_to_zpaths64(const ExPolygons &src, int64_t &base_idx) +{ + ZPaths64 out; + out.reserve(std::accumulate(src.begin(), src.end(), size_t(0), + [](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); })); + for (const ExPolygon &expoly : src) { + out.emplace_back(to_zpath64(expoly.contour.points, base_idx)); + for (const Polygon &hole : expoly.holes) + out.emplace_back(to_zpath64(hole.points, base_idx)); + ++base_idx; + } + return out; +} + +// Convert multiple expolygons into zpaths with the same Z. +template +inline ZPaths64 expolygons_to_zpaths64_with_same_z(const ExPolygons &src, int64_t z) +{ + ZPaths64 out; + out.reserve(std::accumulate(src.begin(), src.end(), size_t(0), + [](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); })); + for (const ExPolygon &expoly : src) { + out.emplace_back(to_zpath64(expoly.contour.points, z)); + for (const Polygon &hole : expoly.holes) + out.emplace_back(to_zpath64(hole.points, z)); + } + return out; +} + +// Convert a zpath back to 2D Points. +// If Open, then duplicate the first point at the end. +template +inline Points from_zpath64(const ZPath64 &path) +{ + Points out; + if (!path.empty()) { + out.reserve(path.size() + (Open ? 1 : 0)); + for (const ZPoint64 &p : path) out.emplace_back(p.x, p.y); + if (Open) out.emplace_back(out.front()); + } + return out; +} + +// Convert multiple zpaths back to 2D paths. +template +inline void from_zpaths64(const ZPaths64 &paths, VecOfPoints &out) +{ + out.reserve(out.size() + paths.size()); + for (const ZPath64 &path : paths) out.emplace_back(from_zpath64(path)); +} +template +inline VecOfPoints from_zpaths64(const ZPaths64 &paths) +{ + VecOfPoints out; + from_zpaths64(paths, out); + return out; +} + +// Intersection visitor for Clipper2 (zCallback_). +class Clipper2ZIntersectionVisitor +{ +public: + using Intersection = std::pair; + using Intersections = std::vector; + + Clipper2ZIntersectionVisitor(Intersections &intersections) : m_intersections(intersections) {} + + void reset() { m_intersections.clear(); } + + void operator()(const ZPoint64 &e1bot, const ZPoint64 &e1top, const ZPoint64 &e2bot, const ZPoint64 &e2top, ZPoint64 &pt) + { + std::array srcs{e1bot.z, e1top.z, e2bot.z, e2top.z}; + std::sort(srcs.begin(), srcs.end()); + auto it = std::unique(srcs.begin(), srcs.end()); + int new_size = std::distance(srcs.begin(), it); + assert(new_size == 1 || new_size == 2); + if (new_size == 1) { + pt.z = srcs[0]; + } + else if(new_size == 2){ + m_intersections.emplace_back(srcs[0], srcs[1]); + pt.z = -int64_t(m_intersections.size()); + } + } + + auto clipper_callback() + { + return [this](const ZPoint64 &e1bot, const ZPoint64 &e1top, + const ZPoint64 &e2bot, const ZPoint64 &e2top, ZPoint64 &pt) { + return (*this)(e1bot, e1top, e2bot, e2top, pt); }; + } + + const Intersections &intersections() const { return m_intersections; } + +private: + Intersections &m_intersections; +}; + +}} // namespace Slic3r::Clipper2ZUtils +#endif // slic3r_Clipper2ZUtils_hpp_ \ No newline at end of file