diff --git a/.github/workflows/actions_cpp.yml b/.github/workflows/actions_cpp.yml index 16919b5a..30463ce6 100644 --- a/.github/workflows/actions_cpp.yml +++ b/.github/workflows/actions_cpp.yml @@ -7,12 +7,12 @@ jobs: windows-latest: runs-on: 'windows-latest' steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: '16' - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v1.0.2 + uses: microsoft/setup-msbuild@v2 - name: Build run: | mkdir CPP/build @@ -26,8 +26,8 @@ jobs: ubuntu-latest-gcc-default: runs-on: 'ubuntu-latest' steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: '16' - name: Build @@ -43,8 +43,8 @@ jobs: ubuntu-latest-gcc-11: runs-on: 'ubuntu-latest' steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: '16' - name: Install gcc 11 @@ -65,8 +65,8 @@ jobs: ubuntu-latest-clang-default: runs-on: 'ubuntu-latest' steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: '16' - name: Build @@ -81,22 +81,22 @@ jobs: run: | cd CPP/build env CTEST_OUTPUT_ON_FAILURE=1 make test - ubuntu-latest-clang-13: + ubuntu-latest-clang-17: runs-on: 'ubuntu-latest' steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: '16' - - name: Install clang 13 + - name: Install clang 17 run: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh - sudo ./llvm.sh 13 + sudo ./llvm.sh 17 - name: Build run: | - export CC=/usr/bin/clang-13 - export CXX=/usr/bin/clang++-13 + export CC=/usr/bin/clang-17 + export CXX=/usr/bin/clang++-17 mkdir CPP/build cd CPP/build cmake .. -DCLIPPER2_TESTS=ON @@ -108,8 +108,8 @@ jobs: macos-latest: runs-on: 'macos-latest' steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: '16' - name: Build diff --git a/.github/workflows/actions_csharp.yml b/.github/workflows/actions_csharp.yml index 6a54caa2..0f9555f4 100644 --- a/.github/workflows/actions_csharp.yml +++ b/.github/workflows/actions_csharp.yml @@ -11,15 +11,15 @@ jobs: run: working-directory: ./CSharp steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Copy Clipper2Lib to USINGZ directory run: cp Clipper2Lib/*.cs USINGZ/ - name: Setup .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | - 3.1.x 6.0.x + 8.0.x - name: Restore run: dotnet restore - name: Build diff --git a/CPP/BenchMark/GetIntersectPtBenchmark.cpp b/CPP/BenchMark/GetIntersectPtBenchmark.cpp index 229bcff1..ac9cef30 100644 --- a/CPP/BenchMark/GetIntersectPtBenchmark.cpp +++ b/CPP/BenchMark/GetIntersectPtBenchmark.cpp @@ -29,7 +29,7 @@ struct SetConsoleTextColor public: SetConsoleTextColor(TextColor color) : _color(color) {}; - static friend std::ostream& operator<< (std::ostream& out, SetConsoleTextColor const& scc) + friend std::ostream& operator<< (std::ostream& out, SetConsoleTextColor const& scc) { return out << "\x1B[" << scc._color << "m"; } diff --git a/CPP/BenchMark/PointInPolygonBenchmark.cpp b/CPP/BenchMark/PointInPolygonBenchmark.cpp index 353ddbd7..38795e2a 100644 --- a/CPP/BenchMark/PointInPolygonBenchmark.cpp +++ b/CPP/BenchMark/PointInPolygonBenchmark.cpp @@ -28,7 +28,7 @@ struct SetConsoleTextColor public: SetConsoleTextColor(ConsoleTextColor color) : _color(color) {}; - static friend std::ostream& operator<< (std::ostream& out, SetConsoleTextColor const& scc) + friend std::ostream& operator<< (std::ostream& out, SetConsoleTextColor const& scc) { return out << "\x1B[" << scc._color << "m"; } @@ -195,7 +195,7 @@ inline PointInPolygonResult PIP2(const Point64& pt, const Path64& polygon) } ///////////////////////////////////////////////////////// -// PIP3: An entirely different algorithm for comparision. +// PIP3: An entirely different algorithm for comparison. // "Optimal Reliable Point-in-Polygon Test and // Differential Coding Boolean Operations on Polygons" // by Jianqiang Hao et al. diff --git a/CPP/CMakeLists.txt b/CPP/CMakeLists.txt index 1e8b0271..fe9e518b 100644 --- a/CPP/CMakeLists.txt +++ b/CPP/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.15) -project(Clipper2 VERSION 1.3.0 LANGUAGES C CXX) +project(Clipper2 VERSION 1.5.2 LANGUAGES C CXX) set(CMAKE_POSITION_INDEPENDENT_CODE ON) if(NOT DEFINED CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 17) @@ -98,7 +98,7 @@ if (NOT (CLIPPER2_USINGZ STREQUAL "OFF")) if (MSVC) target_compile_options(Clipper2Z PRIVATE /W4 /WX) else() - target_compile_options(Clipper2Z PRIVATE -Wall -Wextra -Wpedantic -Werror -Wno-c++20-compat) + target_compile_options(Clipper2Z PRIVATE -Wall -Wextra -Wpedantic -Werror -Wno-c++20-compat -Wsign-conversion) target_link_libraries(Clipper2Z PUBLIC -lm) endif() endif() diff --git a/CPP/Clipper2.pc.cmakein b/CPP/Clipper2.pc.cmakein index 5632c936..0a5d6bab 100644 --- a/CPP/Clipper2.pc.cmakein +++ b/CPP/Clipper2.pc.cmakein @@ -1,7 +1,7 @@ prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} -libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ -includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ Name: Clipper2@PCFILE_LIB_SUFFIX@ Description: A Polygon Clipping and Offsetting library in C++ diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index 8dc40755..ab71aeb0 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -1,25 +1,23 @@ /******************************************************************************* * Author : Angus Johnson * * Date : 12 May 2024 * -* Website : http://www.angusj.com * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2024 * * Purpose : Core Clipper Library structures and functions * -* License : http://www.boost.org/LICENSE_1_0.txt * +* 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 -#include #include -#include "clipper2/clipper.version.h" +#include namespace Clipper2Lib { @@ -30,7 +28,7 @@ namespace Clipper2Lib public: explicit Clipper2Exception(const char* description) : m_descr(description) {} - virtual const char* what() const throw() override { return m_descr.c_str(); } + virtual const char* what() const noexcept override { return m_descr.c_str(); } private: std::string m_descr; }; @@ -87,6 +85,9 @@ namespace Clipper2Lib 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 @@ -106,6 +107,10 @@ namespace Clipper2Lib //https://en.wikipedia.org/wiki/Nonzero-rule enum class FillRule { EvenOdd, NonZero, Positive, Negative }; +#ifdef USINGZ + using z_type = int64_t; +#endif + // Point ------------------------------------------------------------------------ template @@ -113,10 +118,10 @@ namespace Clipper2Lib T x; T y; #ifdef USINGZ - int64_t z; + z_type z; template - inline void Init(const T2 x_ = 0, const T2 y_ = 0, const int64_t z_ = 0) + 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) @@ -136,7 +141,7 @@ namespace Clipper2Lib explicit Point() : x(0), y(0), z(0) {}; template - Point(const T2 x_, const T2 y_, const int64_t z_ = 0) + Point(const T2 x_, const T2 y_, const z_type z_ = 0) { Init(x_, y_); z = z_; @@ -149,7 +154,7 @@ namespace Clipper2Lib } template - explicit Point(const Point& p, int64_t z_) + explicit Point(const Point& p, z_type z_) { Init(p.x, p.y, z_); } @@ -159,7 +164,7 @@ namespace Clipper2Lib return Point(x * scale, y * scale, z); } - void SetZ(const int64_t z_value) { z = z_value; } + void SetZ(const z_type z_value) { z = z_value; } friend std::ostream& operator<<(std::ostream& os, const Point& point) { @@ -323,10 +328,10 @@ namespace Clipper2Lib { Path result; result.reserve(4); - result.push_back(Point(left, top)); - result.push_back(Point(right, top)); - result.push_back(Point(right, bottom)); - result.push_back(Point(left, bottom)); + result.emplace_back(left, top); + result.emplace_back(right, top); + result.emplace_back(right, bottom); + result.emplace_back(left, bottom); return result; } @@ -361,6 +366,22 @@ namespace Clipper2Lib 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; @@ -594,13 +615,13 @@ namespace Clipper2Lib result.reserve(path.size()); typename Path::const_iterator path_iter = path.cbegin(); Point first_pt = *path_iter++, last_pt = first_pt; - result.push_back(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.push_back(last_pt); + result.emplace_back(last_pt); } } if (!is_closed_path) return result; @@ -618,7 +639,7 @@ namespace Clipper2Lib for (typename Paths::const_iterator paths_citer = paths.cbegin(); paths_citer != paths.cend(); ++paths_citer) { - result.push_back(StripNearEqual(*paths_citer, max_dist_sqrd, is_closed_path)); + result.emplace_back(std::move(StripNearEqual(*paths_citer, max_dist_sqrd, is_closed_path))); } return result; } @@ -763,7 +784,7 @@ namespace Clipper2Lib const Point& line1, const Point& line2) { //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) - //see http://en.wikipedia.org/wiki/Perpendicular_distance + //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); diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h index a4c8fd82..f4e1e183 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h @@ -1,26 +1,20 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 17 April 2024 * -* Website : http://www.angusj.com * +* Date : 17 September 2024 * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2024 * * Purpose : This is the main polygon clipping module * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #ifndef CLIPPER_ENGINE_H #define CLIPPER_ENGINE_H -#include -#include //#541 -#include +#include "clipper2/clipper.core.h" #include -#include #include -#include #include -#include "clipper2/clipper.core.h" - namespace Clipper2Lib { struct Scanline; @@ -32,13 +26,13 @@ namespace Clipper2Lib { struct HorzSegment; //Note: all clipping operations except for Difference are commutative. - enum class ClipType { None, Intersection, Union, Difference, Xor }; + enum class ClipType { NoClip, Intersection, Union, Difference, Xor }; enum class PathType { Subject, Clip }; - enum class JoinWith { None, Left, Right }; + enum class JoinWith { NoJoin, Left, Right }; enum class VertexFlags : uint32_t { - None = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8 + Empty = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8 }; constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b) @@ -55,7 +49,7 @@ namespace Clipper2Lib { Point64 pt; Vertex* next = nullptr; Vertex* prev = nullptr; - VertexFlags flags = VertexFlags::None; + VertexFlags flags = VertexFlags::Empty; }; struct OutPt { @@ -131,7 +125,7 @@ namespace Clipper2Lib { 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::None; + JoinWith join_with = JoinWith::NoJoin; }; struct LocalMinima { @@ -167,7 +161,7 @@ namespace Clipper2Lib { }; #ifdef USINGZ - typedef std::function ZCallback64; typedef std::function vertex_lists_; std::priority_queue scanline_list_; IntersectNodeList intersect_nodes_; - HorzSegmentList horz_seg_list_; + HorzSegmentList horz_seg_list_; std::vector horz_join_list_; void Reset(); inline void InsertScanline(int64_t y); @@ -343,6 +337,7 @@ namespace Clipper2Lib { 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); @@ -363,10 +358,7 @@ namespace Clipper2Lib { PolyPath64* AddChild(const Path64& path) override { - auto p = std::make_unique(this); - auto* result = childs_.emplace_back(std::move(p)).get(); - result->polygon_ = path; - return result; + return childs_.emplace_back(std::make_unique(this, path)).get(); } void Clear() override @@ -401,6 +393,19 @@ namespace Clipper2Lib { 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); } @@ -423,19 +428,12 @@ namespace Clipper2Lib { PolyPathD* AddChild(const Path64& path) override { - int error_code = 0; - auto p = std::make_unique(this); - PolyPathD* result = childs_.emplace_back(std::move(p)).get(); - result->polygon_ = ScalePath(path, scale_, error_code); - return result; + return childs_.emplace_back(std::make_unique(this, path)).get(); } PolyPathD* AddChild(const PathD& path) { - auto p = std::make_unique(this); - PolyPathD* result = childs_.emplace_back(std::move(p)).get(); - result->polygon_ = path; - return result; + return childs_.emplace_back(std::make_unique(this, path)).get(); } void Clear() override diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index 53a44536..79856e2e 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -1,16 +1,16 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 14 May 2024 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2024 * +* 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 : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ /* Boolean clipping: - cliptype: None=0, Intersection=1, Union=2, Difference=3, Xor=4 + cliptype: NoClip=0, Intersection=1, Union=2, Difference=3, Xor=4 fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3 Polygon offsetting (inflate/deflate): @@ -19,73 +19,104 @@ 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 converted into very simple array data structures -(of either int64_t for CPath64 or double for CPathD) that can be parsed by -just about any programming language. +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 consecutive x and y path coordinates preceeded by -a pair of values containing the path's length (N) and a 0 value. -__________________________________ -|counter|coord1|coord2|...|coordN| -|N, 0 |x1, y1|x2, y2|...|xN, yN| -__________________________________ +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 containing any number of consecutive CPath64 or -CPathD structures. But preceeding these consecutive paths, there is pair of -values that contain the total length of the array structure (A) and the -number of CPath64 or CPathD it contains (C). The space these structures will -occupy in memory = A * sizeof(int64_t) or A * sizeof(double) respectively. -_______________________________ -|counter|path1|path2|...|pathC| -|A , C | | -_______________________________ +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: -These are also arrays consisting of CPolyPath structures that represent -individual paths in a tree structure. However, the very first (ie top) -CPolyPath is just the tree container that doesn't have a path. And because -of that, its structure will be very slightly different from the remaining -CPolyPath. This difference will be discussed below. +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 are simple arrays consisting of a series of path coordinates followed -by any number of child (ie nested) CPolyPath. Preceeding these are two values -indicating the length of the path (N) and the number of child CPolyPath (C). -____________________________________________________________ -|counter|coord1|coord2|...|coordN| child1|child2|...|childC| -|N , C |x1, y1|x2, y2|...|xN, yN| | -____________________________________________________________ - -As mentioned above, the very first CPolyPath structure is just a container -that owns (both directly and indirectly) every other CPolyPath in the tree. -Since this first CPolyPath has no path, instead of a path length, its very -first value will contain the total length of the CPolytree array (not its -total bytes length). - -Again, all theses exported structures (CPaths64, CPathsD, CPolyTree64 & -CPolyTreeD) are arrays of either type int64_t or double, and the first -value in these arrays will always be the length of that array. - -These array structures are allocated in heap memory which will eventually -need to be released. However, since applications dynamically linking to -these functions may use different memory managers, the only safe way to -free up this memory is to use the exported DisposeArray64 and -DisposeArrayD functions (see below). -*/ +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 -#include - #include "clipper2/clipper.core.h" #include "clipper2/clipper.engine.h" #include "clipper2/clipper.offset.h" #include "clipper2/clipper.rectclip.h" +#include namespace Clipper2Lib { @@ -127,6 +158,12 @@ inline Rect CRectToRect(const CRect& rect) return result; } +template +inline T1 Reinterpret(T2 value) { + return *reinterpret_cast(&value); +} + + #ifdef _WIN32 #define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport) #else @@ -178,11 +215,22 @@ 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); @@ -197,6 +245,15 @@ EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, // 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) @@ -206,30 +263,47 @@ static void GetPathCountAndCPathsArrayLen(const Paths& paths, for (const Path& path : paths) if (path.size()) { - array_len += path.size() * 2 + 2; + array_len += path.size() * EXPORT_VERTEX_DIMENSIONALITY + 2; ++cnt; } } -static size_t GetPolyPath64ArrayLen(const PolyPath64& pp) +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() * 2; + result += pp.Polygon().size() * EXPORT_VERTEX_DIMENSIONALITY; //plus nested children :) for (size_t i = 0; i < pp.Count(); ++i) - result += GetPolyPath64ArrayLen(*pp[i]); + result += GetPolyPathArrayLenD(*pp[i]); return result; } -static void GetPolytreeCountAndCStorageSize(const PolyTree64& tree, +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 = GetPolyPath64ArrayLen(tree); + array_len = GetPolyPathArrayLenD(tree); } template -static T* CreateCPaths(const Paths& paths) +static T* CreateCPathsFromPathsT(const Paths& paths) { size_t cnt = 0, array_len = 0; GetPathCountAndCPathsArrayLen(paths, cnt, array_len); @@ -245,11 +319,38 @@ static T* CreateCPaths(const Paths& paths) { *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) { @@ -268,13 +369,16 @@ CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale) { *v++ = pt.x * scale; *v++ = pt.y * scale; +#ifdef USINGZ + *v++ = Reinterpret(pt.z); +#endif } } return result; } template -static Path ConvertCPath(T* path) +static Path ConvertCPathToPathT(T* path) { Path result; if (!path) return result; @@ -284,14 +388,19 @@ static Path ConvertCPath(T* path) result.reserve(cnt); for (size_t j = 0; j < cnt; ++j) { - T x = *v++, y = *v++; - result.push_back(Point(x, y)); + 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 ConvertCPaths(T* paths) +static Paths ConvertCPathsToPathsT(T* paths) { Paths result; if (!paths) return result; @@ -301,19 +410,45 @@ static Paths ConvertCPaths(T* paths) for (size_t i = 0; i < cnt; ++i) { size_t cnt2 = static_cast(*v); - v += 2; + v += 2; Path path; path.reserve(cnt2); for (size_t j = 0; j < cnt2; ++j) { T x = *v++, y = *v++; - path.push_back(Point(x, y)); +#ifdef USINGZ + z_type z = Reinterpret(*v++); + path.emplace_back(x, y, z); +#else + path.emplace_back(x, y); +#endif } - result.push_back(path); + 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) { @@ -333,42 +468,78 @@ static Paths64 ConvertCPathsDToPaths64(const CPathsD paths, double scale) { double x = *v++ * scale; double y = *v++ * scale; - path.push_back(Point64(x, y)); +#ifdef USINGZ + z_type z = Reinterpret(*v++); + path.emplace_back(x, y, z); +#else + path.emplace_back(x, y); +#endif } - result.push_back(path); + result.emplace_back(std::move(path)); } return result; } -template -static void CreateCPolyPath(const PolyPath64* pp, T*& v, T scale) +static void CreateCPolyPath64(const PolyPath64* pp, int64_t*& v) { - *v++ = static_cast(pp->Polygon().size()); - *v++ = static_cast(pp->Count()); + *v++ = static_cast(pp->Polygon().size()); + *v++ = static_cast(pp->Count()); for (const Point64& pt : pp->Polygon()) { - *v++ = static_cast(pt.x * scale); - *v++ = static_cast(pt.y * scale); + *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) - CreateCPolyPath(pp->Child(i), v, scale); + CreateCPolyPath64(pp->Child(i), v); } -template -static T* CreateCPolyTree(const PolyTree64& tree, T scale) +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) { - if (scale == 0) scale = 1; size_t cnt, array_len; - GetPolytreeCountAndCStorageSize(tree, cnt, array_len); + GetPolytreeCountAndCStorageSize64(tree, cnt, array_len); if (!cnt) return nullptr; // allocate storage - T* result = new T[array_len]; - T* v = result; + 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; +} - *v++ = static_cast(array_len); - *v++ = static_cast(tree.Count()); +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) - CreateCPolyPath(tree.Child(i), v, scale); + CreateCPolyPathD(tree.Child(i), v); return result; } @@ -391,20 +562,24 @@ EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, if (fillrule > static_cast(FillRule::Negative)) return -3; Paths64 sub, sub_open, clp, sol, sol_open; - sub = ConvertCPaths(subjects); - sub_open = ConvertCPaths(subjects_open); - clp = ConvertCPaths(clips); + 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 = CreateCPaths(sol); - solution_open = CreateCPaths(sol_open); + solution = CreateCPathsFromPathsT(sol); + solution_open = CreateCPathsFromPathsT(sol_open); return 0; //success !! } @@ -417,22 +592,26 @@ EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype, if (cliptype > static_cast(ClipType::Xor)) return -4; if (fillrule > static_cast(FillRule::Negative)) return -3; Paths64 sub, sub_open, clp, sol_open; - sub = ConvertCPaths(subjects); - sub_open = ConvertCPaths(subjects_open); - clp = ConvertCPaths(clips); + 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 = CreateCPolyTree(tree, (int64_t)1); - solution_open = CreateCPaths(sol_open); + sol_tree = CreateCPolyTree64(tree); + solution_open = CreateCPathsFromPathsT(sol_open); return 0; //success !! } @@ -445,23 +624,27 @@ EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, 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); + //const double scale = std::pow(10, precision); - Paths64 sub, sub_open, clp, sol, sol_open; - sub = ConvertCPathsDToPaths64(subjects, scale); - sub_open = ConvertCPathsDToPaths64(subjects_open, scale); - clp = ConvertCPathsDToPaths64(clips, scale); + PathsD sub, sub_open, clp, sol, sol_open; + sub = ConvertCPathsToPathsT(subjects); + sub_open = ConvertCPathsToPathsT(subjects_open); + clp = ConvertCPathsToPathsT(clips); - Clipper64 clipper; + 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 = CreateCPathsDFromPaths64(sol, 1 / scale); - solution_open = CreateCPathsDFromPaths64(sol_open, 1 / scale); + solution = CreateCPathsDFromPathsD(sol); + solution_open = CreateCPathsDFromPathsD(sol_open); return 0; } @@ -474,27 +657,30 @@ EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype, 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); + //double scale = std::pow(10, precision); int err = 0; - Paths64 sub, sub_open, clp, sol_open; - sub = ConvertCPathsDToPaths64(subjects, scale); - sub_open = ConvertCPathsDToPaths64(subjects_open, scale); - clp = ConvertCPathsDToPaths64(clips, scale); + PathsD sub, sub_open, clp, sol_open; + sub = ConvertCPathsToPathsT(subjects); + sub_open = ConvertCPathsToPathsT(subjects_open); + clp = ConvertCPathsToPathsT(clips); - PolyTree64 tree; - Clipper64 clipper; + 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 = CreateCPolyTree(tree, 1/scale); - solution_open = CreateCPathsDFromPaths64(sol_open, 1 / scale); + solution = CreateCPolyTreeD(tree); + solution_open = CreateCPathsDFromPathsD(sol_open); return 0; //success !! } @@ -503,13 +689,13 @@ EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths, double arc_tolerance, bool reverse_solution) { Paths64 pp; - pp = ConvertCPaths(paths); + 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 CreateCPaths(result); + return CreateCPathsFromPathsT(result); } EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, @@ -525,18 +711,49 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, 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 = ConvertCPaths(paths); + Paths64 pp = ConvertCPathsToPathsT(paths); Paths64 result = rc.Execute(pp); - return CreateCPaths(result); + return CreateCPathsFromPathsT(result); } EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int precision) @@ -560,9 +777,9 @@ EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, if (CRectIsEmpty(rect) || !paths) return nullptr; Rect64 r = CRectToRect(rect); class RectClipLines64 rcl (r); - Paths64 pp = ConvertCPaths(paths); + Paths64 pp = ConvertCPathsToPathsT(paths); Paths64 result = rcl.Execute(pp); - return CreateCPaths(result); + return CreateCPathsFromPathsT(result); } EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, @@ -581,20 +798,35 @@ EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, EXTERN_DLL_EXPORT CPaths64 MinkowskiSum64(const CPath64& cpattern, const CPath64& cpath, bool is_closed) { - Path64 path = ConvertCPath(cpath); - Path64 pattern = ConvertCPath(cpattern); + Path64 path = ConvertCPathToPathT(cpath); + Path64 pattern = ConvertCPathToPathT(cpattern); Paths64 solution = MinkowskiSum(pattern, path, is_closed); - return CreateCPaths(solution); + return CreateCPathsFromPathsT(solution); } EXTERN_DLL_EXPORT CPaths64 MinkowskiDiff64(const CPath64& cpattern, const CPath64& cpath, bool is_closed) { - Path64 path = ConvertCPath(cpath); - Path64 pattern = ConvertCPath(cpattern); + Path64 path = ConvertCPathToPathT(cpath); + Path64 pattern = ConvertCPathToPathT(cpattern); Paths64 solution = MinkowskiDiff(pattern, path, is_closed); - return CreateCPaths(solution); + 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; } -} // end Clipper2Lib namespace +EXTERN_DLL_EXPORT void SetZCallbackD(DLLZCallbackD callback) +{ + dllCallbackD = callback; +} +#endif + +} #endif // CLIPPER2_EXPORT_H diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.h b/CPP/Clipper2Lib/include/clipper2/clipper.h index 4f55df87..b75bbd35 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.h @@ -1,24 +1,21 @@ /******************************************************************************* * Author : Angus Johnson * * Date : 27 April 2024 * -* Website : http://www.angusj.com * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2024 * * Purpose : This module provides a simple interface to the Clipper Library * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #ifndef CLIPPER_H #define CLIPPER_H -#include -#include -#include - #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 namespace Clipper2Lib { @@ -272,14 +269,14 @@ namespace Clipper2Lib { inline void PolyPathToPaths64(const PolyPath64& polypath, Paths64& paths) { - paths.push_back(polypath.Polygon()); + paths.emplace_back(polypath.Polygon()); for (const auto& child : polypath) PolyPathToPaths64(*child, paths); } inline void PolyPathToPathsD(const PolyPathD& polypath, PathsD& paths) { - paths.push_back(polypath.Polygon()); + paths.emplace_back(polypath.Polygon()); for (const auto& child : polypath) PolyPathToPathsD(*child, paths); } @@ -348,9 +345,9 @@ namespace Clipper2Lib { result.reserve(array_size / 2); for (size_t i = 0; i < array_size; i +=2) #ifdef USINGZ - result.push_back( U{ an_array[i], an_array[i + 1], 0} ); + result.emplace_back( an_array[i], an_array[i + 1], 0 ); #else - result.push_back( U{ an_array[i], an_array[i + 1]} ); + result.emplace_back( an_array[i], an_array[i + 1] ); #endif } @@ -518,20 +515,20 @@ namespace Clipper2Lib { } prevIt = srcIt++; - dst.push_back(*prevIt); + dst.emplace_back(*prevIt); for (; srcIt != stop; ++srcIt) { if (!IsCollinear(*prevIt, *srcIt, *(srcIt + 1))) { prevIt = srcIt; - dst.push_back(*prevIt); + dst.emplace_back(*prevIt); } } if (is_open_path) - dst.push_back(*srcIt); + dst.emplace_back(*srcIt); else if (!IsCollinear(*prevIt, *stop, dst[0])) - dst.push_back(*stop); + dst.emplace_back(*stop); else { while (dst.size() > 2 && @@ -582,7 +579,7 @@ namespace Clipper2Lib { } template - inline Path Ellipse(const Rect& rect, int steps = 0) + inline Path Ellipse(const Rect& rect, size_t steps = 0) { return Ellipse(rect.MidPoint(), static_cast(rect.Width()) *0.5, @@ -591,22 +588,22 @@ namespace Clipper2Lib { template inline Path Ellipse(const Point& center, - double radiusX, double radiusY = 0, int steps = 0) + 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)); + 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.push_back(Point(center.x + radiusX, static_cast(center.y))); - for (int i = 1; i < steps; ++i) + result.emplace_back(center.x + radiusX, static_cast(center.y)); + for (size_t i = 1; i < steps; ++i) { - result.push_back(Point(center.x + radiusX * dx, center.y + radiusY * dy)); + result.emplace_back(center.x + radiusX * dx, center.y + radiusY * dy); double x = dx * co - dy * si; dy = dy * co + dx * si; dx = x; @@ -700,7 +697,7 @@ namespace Clipper2Lib { Path result; result.reserve(len); for (typename Path::size_type i = 0; i < len; ++i) - if (!flags[i]) result.push_back(path[i]); + if (!flags[i]) result.emplace_back(path[i]); return result; } @@ -711,7 +708,7 @@ namespace Clipper2Lib { Paths result; result.reserve(paths.size()); for (const auto& path : paths) - result.push_back(SimplifyPath(path, epsilon, isClosedPath)); + result.emplace_back(std::move(SimplifyPath(path, epsilon, isClosedPath))); return result; } @@ -749,7 +746,7 @@ namespace Clipper2Lib { result.reserve(len); for (typename Path::size_type i = 0; i < len; ++i) if (flags[i]) - result.push_back(path[i]); + result.emplace_back(path[i]); return result; } diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.minkowski.h b/CPP/Clipper2Lib/include/clipper2/clipper.minkowski.h index a3ddcf86..3a85ba5d 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.minkowski.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.minkowski.h @@ -1,18 +1,15 @@ /******************************************************************************* * Author : Angus Johnson * * Date : 1 November 2023 * -* Website : http://www.angusj.com * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Minkowski Sum and Difference * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #ifndef CLIPPER_MINKOWSKI_H #define CLIPPER_MINKOWSKI_H -#include -#include -#include #include "clipper2/clipper.core.h" namespace Clipper2Lib @@ -35,7 +32,7 @@ namespace Clipper2Lib Path64 path2(pattern.size()); std::transform(pattern.cbegin(), pattern.cend(), path2.begin(), [p](const Point64& pt2) {return p + pt2; }); - tmp.push_back(path2); + tmp.emplace_back(std::move(path2)); } } else @@ -45,7 +42,7 @@ namespace Clipper2Lib Path64 path2(pattern.size()); std::transform(pattern.cbegin(), pattern.cend(), path2.begin(), [p](const Point64& pt2) {return p - pt2; }); - tmp.push_back(path2); + tmp.emplace_back(std::move(path2)); } } @@ -59,14 +56,14 @@ namespace Clipper2Lib Path64 quad; quad.reserve(4); { - quad.push_back(tmp[g][h]); - quad.push_back(tmp[i][h]); - quad.push_back(tmp[i][j]); - quad.push_back(tmp[g][j]); + 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.push_back(quad); + result.emplace_back(std::move(quad)); h = j; } g = i; diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.offset.h b/CPP/Clipper2Lib/include/clipper2/clipper.offset.h index 19ee9676..90ccd5ec 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.offset.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.offset.h @@ -1,10 +1,10 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 24 March 2024 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2024 * +* Date : 22 January 2025 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2025 * * Purpose : Path Offset (Inflate/Shrink) * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #ifndef CLIPPER_OFFSET_H_ @@ -12,6 +12,7 @@ #include "clipper.core.h" #include "clipper.engine.h" +#include namespace Clipper2Lib { @@ -34,7 +35,7 @@ class ClipperOffset { class Group { public: Paths64 paths_in; - int lowest_path_idx = -1; + std::optional lowest_path_idx{}; bool is_reversed = false; JoinType join_type; EndType end_type; @@ -96,7 +97,7 @@ class ClipperOffset { void AddPaths(const Paths64& paths, JoinType jt_, EndType et_); void Clear() { groups_.clear(); norms.clear(); }; - void Execute(double delta, Paths64& paths); + void Execute(double delta, Paths64& sols_64); void Execute(double delta, PolyTree64& polytree); void Execute(DeltaCallback64 delta_cb, Paths64& paths); diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h b/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h index ff043f25..e7cf2f45 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h @@ -1,23 +1,22 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 1 November 2023 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2023 * +* Date : 5 July 2024 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2024 * * Purpose : FAST rectangular clipping * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #ifndef CLIPPER_RECTCLIP_H #define CLIPPER_RECTCLIP_H -#include -#include -#include #include "clipper2/clipper.core.h" +#include namespace Clipper2Lib { + // Location: the order is important here, see StartLocsIsClockwise() enum class Location { Left, Top, Right, Bottom, Inside }; class OutPt2; @@ -26,10 +25,10 @@ namespace Clipper2Lib class OutPt2 { public: Point64 pt; - size_t owner_idx; - OutPt2List* edge; - OutPt2* next; - OutPt2* prev; + size_t owner_idx = 0; + OutPt2List* edge = nullptr; + OutPt2* next = nullptr; + OutPt2* prev = nullptr; }; //------------------------------------------------------------------------------ @@ -50,9 +49,9 @@ namespace Clipper2Lib OutPt2List edges_[8]; // clockwise and counter-clockwise std::vector start_locs_; void CheckEdges(); - void TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw); + void TidyEdges(size_t idx, OutPt2List& cw, OutPt2List& ccw); void GetNextLocation(const Path64& path, - Location& loc, int& i, int highI); + 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); diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.version.h b/CPP/Clipper2Lib/include/clipper2/clipper.version.h index d7644067..661b0f1c 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.version.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.version.h @@ -1,6 +1,6 @@ #ifndef CLIPPER_VERSION_H #define CLIPPER_VERSION_H -constexpr auto CLIPPER2_VERSION = "1.3.0"; +constexpr auto CLIPPER2_VERSION = "1.5.2"; #endif // CLIPPER_VERSION_H diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index de288d7f..927e7a75 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,21 +1,15 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 27 April 2024 * -* Website : http://www.angusj.com * +* Date : 17 September 2024 * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2024 * * Purpose : This is the main polygon clipping module * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ -#include -#include -#include -#include -#include -#include - #include "clipper2/clipper.engine.h" #include "clipper2/clipper.h" +#include // https://github.com/AngusJohnson/Clipper2/discussions/334 // #discussioncomment-4248602 @@ -85,7 +79,7 @@ namespace Clipper2Lib { inline bool IsOpenEnd(const Vertex& v) { return (v.flags & (VertexFlags::OpenStart | VertexFlags::OpenEnd)) != - VertexFlags::None; + VertexFlags::Empty; } @@ -220,7 +214,7 @@ namespace Clipper2Lib { inline bool IsMaxima(const Vertex& v) { - return ((v.flags & VertexFlags::LocalMax) != VertexFlags::None); + return ((v.flags & VertexFlags::LocalMax) != VertexFlags::Empty); } @@ -235,12 +229,12 @@ namespace Clipper2Lib { if (e.wind_dx > 0) while ((result->next->pt.y == result->pt.y) && ((result->flags & (VertexFlags::OpenEnd | - VertexFlags::LocalMax)) == VertexFlags::None)) + VertexFlags::LocalMax)) == VertexFlags::Empty)) result = result->next; else while (result->prev->pt.y == result->pt.y && ((result->flags & (VertexFlags::OpenEnd | - VertexFlags::LocalMax)) == VertexFlags::None)) + VertexFlags::LocalMax)) == VertexFlags::Empty)) result = result->prev; if (!IsMaxima(*result)) result = nullptr; // not a maxima return result; @@ -478,7 +472,7 @@ namespace Clipper2Lib { inline bool IsJoined(const Active& e) { - return e.join_with != JoinWith::None; + return e.join_with != JoinWith::NoJoin; } inline void SetOwner(OutRec* outrec, OutRec* new_owner) @@ -517,7 +511,7 @@ namespace Clipper2Lib { while (op2 != op && op2->pt.y > pt.y) op2 = op2->next; if (op2 == op) break; - // must have touched or crossed the pt.Y horizonal + // 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 @@ -564,7 +558,7 @@ namespace Clipper2Lib { 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.push_back(op2->pt); + result.emplace_back(op2->pt); OutPt* prevOp = op2; op2 = op2->next; while (op2 != op) @@ -572,7 +566,7 @@ namespace Clipper2Lib { 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.push_back(op2->pt); + result.emplace_back(op2->pt); prevOp = op2; } op2 = op2->next; @@ -608,19 +602,19 @@ namespace Clipper2Lib { Vertex& vert, PathType polytype, bool is_open) { //make sure the vertex is added only once ... - if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::None) return; + if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::Empty) return; vert.flags = (vert.flags | VertexFlags::LocalMin); - list.push_back(std::make_unique (&vert, polytype, is_open)); + 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(), 0, + std::accumulate(paths.begin(), paths.end(), size_t(0), [](const auto& a, const Path64& path) - {return a + static_cast(path.size()); }); + {return a + path.size(); }); if (total_vertex_count == 0) return; Vertex* vertices = new Vertex[total_vertex_count], * v = vertices; @@ -643,7 +637,7 @@ namespace Clipper2Lib { } curr_v->prev = prev_v; curr_v->pt = pt; - curr_v->flags = VertexFlags::None; + curr_v->flags = VertexFlags::Empty; prev_v = curr_v++; cnt++; } @@ -725,10 +719,10 @@ namespace Clipper2Lib { void ReuseableDataContainer64::AddLocMin(Vertex& vert, PathType polytype, bool is_open) { //make sure the vertex is added only once ... - if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::None) return; + if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::Empty) return; vert.flags = (vert.flags | VertexFlags::LocalMin); - minima_list_.push_back(std::make_unique (&vert, polytype, is_open)); + minima_list_.emplace_back(std::make_unique (&vert, polytype, is_open)); } void ReuseableDataContainer64::AddPaths(const Paths64& paths, @@ -836,9 +830,7 @@ namespace Clipper2Lib { void ClipperBase::AddPath(const Path64& path, PathType polytype, bool is_open) { - Paths64 tmp; - tmp.push_back(path); - AddPaths(tmp, polytype, is_open); + AddPaths(Paths64(1, path), polytype, is_open); } void ClipperBase::AddPaths(const Paths64& paths, PathType polytype, bool is_open) @@ -857,7 +849,7 @@ namespace Clipper2Lib { LocalMinimaList::const_iterator i; for (i = reuseable_data.minima_list_.cbegin(); i != reuseable_data.minima_list_.cend(); ++i) { - minima_list_.push_back(std::make_unique ((*i)->vertex, (*i)->polytype, (*i)->is_open)); + minima_list_.emplace_back(std::make_unique ((*i)->vertex, (*i)->polytype, (*i)->is_open)); if ((*i)->is_open) has_open_paths_ = true; } } @@ -907,10 +899,10 @@ namespace Clipper2Lib { void ClipperBase::AddLocMin(Vertex& vert, PathType polytype, bool is_open) { //make sure the vertex is added only once ... - if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::None) return; + if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::Empty) return; vert.flags = (vert.flags | VertexFlags::LocalMin); - minima_list_.push_back(std::make_unique (&vert, polytype, is_open)); + minima_list_.emplace_back(std::make_unique (&vert, polytype, is_open)); } bool ClipperBase::IsContributingClosed(const Active& e) const @@ -928,11 +920,14 @@ namespace Clipper2Lib { 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::None: + case ClipType::NoClip: return false; case ClipType::Intersection: switch (fillrule_) @@ -978,6 +973,9 @@ namespace Clipper2Lib { 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 } @@ -1208,7 +1206,7 @@ namespace Clipper2Lib { while (PopLocalMinima(bot_y, local_minima)) { - if ((local_minima->vertex->flags & VertexFlags::OpenStart) != VertexFlags::None) + if ((local_minima->vertex->flags & VertexFlags::OpenStart) != VertexFlags::Empty) { left_bound = nullptr; } @@ -1224,7 +1222,7 @@ namespace Clipper2Lib { SetDx(*left_bound); } - if ((local_minima->vertex->flags & VertexFlags::OpenEnd) != VertexFlags::None) + if ((local_minima->vertex->flags & VertexFlags::OpenEnd) != VertexFlags::Empty) { right_bound = nullptr; } @@ -1488,7 +1486,7 @@ namespace Clipper2Lib { { OutRec* result = new OutRec(); result->idx = outrec_list_.size(); - outrec_list_.push_back(result); + outrec_list_.emplace_back(result); result->pts = nullptr; result->owner = nullptr; result->polypath = nullptr; @@ -1631,12 +1629,12 @@ namespace Clipper2Lib { if (Path1InsidePath2(prevOp, newOp)) { newOr->splits = new OutRecList(); - newOr->splits->push_back(outrec); + newOr->splits->emplace_back(outrec); } else { if (!outrec->splits) outrec->splits = new OutRecList(); - outrec->splits->push_back(newOr); + outrec->splits->emplace_back(newOr); } } } @@ -1955,7 +1953,7 @@ namespace Clipper2Lib { else if (IsFront(e1) || (e1.outrec == e2.outrec)) { //this 'else if' condition isn't strictly needed but - //it's sensible to split polygons that ony touch at + //it's sensible to split polygons that only touch at //a common vertex (not at common edges). #ifdef USINGZ @@ -2125,7 +2123,7 @@ namespace Clipper2Lib { using_polytree_ = use_polytrees; Reset(); int64_t y; - if (ct == ClipType::None || !PopScanline(y)) return true; + if (ct == ClipType::NoClip || !PopScanline(y)) return true; while (succeeded_) { @@ -2239,7 +2237,7 @@ namespace Clipper2Lib { HorzJoin join = HorzJoin( DuplicateOp(hs1->left_op, true), DuplicateOp(hs2->left_op, false)); - horz_join_list_.push_back(join); + horz_join_list_.emplace_back(join); } else { @@ -2252,7 +2250,7 @@ namespace Clipper2Lib { HorzJoin join = HorzJoin( DuplicateOp(hs2->left_op, true), DuplicateOp(hs1->left_op, false)); - horz_join_list_.push_back(join); + horz_join_list_.emplace_back(join); } } } @@ -2264,7 +2262,7 @@ namespace Clipper2Lib { if (!toOr->splits) toOr->splits = new OutRecList(); OutRecList::iterator orIter = fromOr->splits->begin(); for (; orIter != fromOr->splits->end(); ++orIter) - toOr->splits->push_back(*orIter); + toOr->splits->emplace_back(*orIter); fromOr->splits->clear(); } @@ -2317,7 +2315,7 @@ namespace Clipper2Lib { or2->owner = or1->owner; if (!or1->splits) or1->splits = new OutRecList(); - or1->splits->push_back(or2); + or1->splits->emplace_back(or2); } else or2->owner = or1; @@ -2376,7 +2374,7 @@ namespace Clipper2Lib { else ip.x = TopX(e2, ip.y); } } - intersect_nodes_.push_back(IntersectNode(&e1, &e2, ip)); + intersect_nodes_.emplace_back(&e1, &e2, ip); } bool ClipperBase::BuildIntersectList(const int64_t top_y) @@ -2497,7 +2495,7 @@ namespace Clipper2Lib { void ClipperBase::AddTrialHorzJoin(OutPt* op) { if (op->outrec->is_open) return; - horz_seg_list_.push_back(HorzSegment(op)); + horz_seg_list_.emplace_back(op); } bool ClipperBase::ResetHorzDirection(const Active& horz, @@ -2655,7 +2653,7 @@ namespace Clipper2Lib { if (horz.outrec) { - //nb: The outrec containining the op returned by IntersectEdges + //nb: The outrec containing the op returned by IntersectEdges //above may no longer be associated with horzEdge. AddTrialHorzJoin(GetLastOp(horz)); } @@ -2789,14 +2787,14 @@ namespace Clipper2Lib { { if (e.join_with == JoinWith::Right) { - e.join_with = JoinWith::None; - e.next_in_ael->join_with = JoinWith::None; + 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::None; - e.prev_in_ael->join_with = JoinWith::None; + e.join_with = JoinWith::NoJoin; + e.prev_in_ael->join_with = JoinWith::NoJoin; AddLocalMinPoly(*e.prev_in_ael, e, pt, true); } } @@ -2899,14 +2897,14 @@ namespace Clipper2Lib { lastPt = op->pt; op2 = op->next; } - path.push_back(lastPt); + path.emplace_back(lastPt); while (op2 != op) { if (op2->pt != lastPt) { lastPt = op2->pt; - path.push_back(lastPt); + path.emplace_back(lastPt); } if (reverse) op2 = op2->prev; @@ -3031,7 +3029,7 @@ namespace Clipper2Lib { { Path64 path; if (BuildPath64(outrec->pts, reverse_solution_, true, path)) - open_paths.push_back(path); + open_paths.emplace_back(std::move(path)); continue; } @@ -3060,9 +3058,9 @@ namespace Clipper2Lib { op2 = op->next; } #ifdef USINGZ - path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z)); + path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z); #else - path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale)); + path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale); #endif while (op2 != op) @@ -3071,9 +3069,9 @@ namespace Clipper2Lib { { lastPt = op2->pt; #ifdef USINGZ - path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z)); + path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z); #else - path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale)); + path.emplace_back(lastPt.x * inv_scale, lastPt.y * inv_scale); #endif } @@ -3137,7 +3135,7 @@ namespace Clipper2Lib { { PathD path; if (BuildPathD(outrec->pts, reverse_solution_, true, path, invScale_)) - open_paths.push_back(path); + open_paths.emplace_back(std::move(path)); continue; } diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index e0959188..23e405ed 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,28 +1,41 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 17 April 2024 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2024 * +* Date : 22 January 2025 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2025 * * Purpose : Path Offset (Inflate/Shrink) * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ -#include #include "clipper2/clipper.h" #include "clipper2/clipper.offset.h" namespace Clipper2Lib { -const double default_arc_tolerance = 0.25; 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 //------------------------------------------------------------------------------ -int GetLowestClosedPathIdx(const Paths64& paths) +std::optional GetLowestClosedPathIdx(const Paths64& paths) { - int result = -1; + std::optional result; Point64 botPt = Point64(INT64_MAX, INT64_MIN); for (size_t i = 0; i < paths.size(); ++i) { @@ -30,7 +43,7 @@ int GetLowestClosedPathIdx(const Paths64& paths) { if ((pt.y < botPt.y) || ((pt.y == botPt.y) && (pt.x >= botPt.x))) continue; - result = static_cast(i); + result = i; botPt.x = pt.x; botPt.y = pt.y; } @@ -38,13 +51,22 @@ int GetLowestClosedPathIdx(const Paths64& paths) return result; } -PointD GetUnitNormal(const Point64& pt1, const Point64& pt2) +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) { - double dx, dy, inverse_hypot; if (pt1 == pt2) return PointD(0.0, 0.0); - dx = static_cast(pt2.x - pt1.x); - dy = static_cast(pt2.y - pt1.y); - inverse_hypot = 1.0 / hypot(dx, dy); + 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); @@ -55,12 +77,6 @@ inline bool AlmostZero(double value, double epsilon = 0.001) return std::fabs(value) < epsilon; } -inline double Hypot(double x, double y) -{ - //see https://stackoverflow.com/a/32436148/359538 - return std::sqrt(x * x + y * y); -} - inline PointD NormalizeVector(const PointD& vec) { double h = Hypot(vec.x, vec.y); @@ -79,7 +95,7 @@ inline bool IsClosedPath(EndType et) return et == EndType::Polygon || et == EndType::Joined; } -inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta) +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); @@ -129,11 +145,11 @@ ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType // 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 >= 0) && Area(paths_in[lowest_path_idx]) < 0; + is_reversed = (lowest_path_idx.has_value()) && Area(paths_in[lowest_path_idx.value()]) < 0; } else { - lowest_path_idx = -1; + lowest_path_idx = std::nullopt; is_reversed = false; } } @@ -144,15 +160,13 @@ ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType void ClipperOffset::AddPath(const Path64& path, JoinType jt_, EndType et_) { - Paths64 paths; - paths.push_back(path); - AddPaths(paths, jt_, 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_.push_back(Group(paths, jt_, et_)); + groups_.emplace_back(paths, jt_, et_); } void ClipperOffset::BuildNormals(const Path64& path) @@ -162,8 +176,8 @@ void ClipperOffset::BuildNormals(const Path64& path) 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.push_back(GetUnitNormal(*path_iter,*(path_iter +1))); - norms.push_back(GetUnitNormal(*path_stop_iter, *(path.cbegin()))); + 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) @@ -190,8 +204,8 @@ void ClipperOffset::DoBevel(const Path64& path, size_t j, size_t k) pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y); #endif } - path_out.push_back(Point64(pt1)); - path_out.push_back(Point64(pt2)); + path_out.emplace_back(pt1); + path_out.emplace_back(pt2); } void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k) @@ -220,17 +234,17 @@ void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k) PointD pt = ptQ; GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt); //get the second intersect point through reflecion - path_out.push_back(Point64(ReflectPoint(pt, ptQ))); - path_out.push_back(Point64(pt)); + 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.push_back(Point64(pt)); + path_out.emplace_back(pt); //get the second intersect point through reflecion - path_out.push_back(Point64(ReflectPoint(pt, ptQ))); + path_out.emplace_back(ReflectPoint(pt, ptQ)); } } @@ -238,14 +252,14 @@ 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.push_back(Point64( + 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)); + path[j].z); #else - path_out.push_back(Point64( + 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].y + (norms[k].y + norms[j].y) * q); #endif } @@ -256,8 +270,7 @@ void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle // 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_) : - std::log10(2 + abs_delta) * default_arc_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); @@ -270,9 +283,9 @@ void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle if (j == k) offsetVec.Negate(); #ifdef USINGZ - path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); + path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z); #else - path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); + 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 @@ -280,12 +293,12 @@ void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y, offsetVec.x * step_sin_ + offsetVec.y * step_cos_); #ifdef USINGZ - path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); + path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z); #else - path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); + path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y); #endif } - path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_)); + 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) @@ -309,28 +322,25 @@ void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size } if (std::fabs(group_delta_) <= floating_point_tolerance) { - path_out.push_back(path[j]); + path_out.emplace_back(path[j]); return; } if (cos_a > -0.999 && (sin_a * group_delta_ < 0)) // test for concavity first (#593) { - // is concave (so insert 3 points that will create a negative region) -#ifdef USINGZ - path_out.push_back(Point64(GetPerpendic(path[j], norms[k], group_delta_), path[j].z)); -#else - path_out.push_back(GetPerpendic(path[j], norms[k], group_delta_)); -#endif - - // this extra point is the only simple way to ensure that path reversals - // (ie over-shrunk paths) are fully cleaned out with the trailing union op. - // However it's probably safe to skip this whenever an angle is almost flat. - if (cos_a < 0.99) path_out.push_back(path[j]); // (#405) - + // 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.push_back(Point64(GetPerpendic(path[j], norms[j], group_delta_), path[j].z)); + 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.push_back(GetPerpendic(path[j], norms[j], group_delta_)); + 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) @@ -357,7 +367,7 @@ 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->push_back(path_out); + solution->emplace_back(path_out); } void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path) @@ -368,7 +378,7 @@ void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path) //rebuild normals std::reverse(norms.begin(), norms.end()); - norms.push_back(norms[0]); + norms.emplace_back(norms[0]); norms.erase(norms.begin()); NegatePath(norms); @@ -381,7 +391,7 @@ void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path) if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0); if (std::fabs(group_delta_) <= floating_point_tolerance) - path_out.push_back(path[0]); + path_out.emplace_back(path[0]); else { switch (end_type_) @@ -413,7 +423,7 @@ void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path) group_delta_ = deltaCallback64_(path, norms, highI, highI); if (std::fabs(group_delta_) <= floating_point_tolerance) - path_out.push_back(path[highI]); + path_out.emplace_back(path[highI]); else { switch (end_type_) @@ -432,7 +442,7 @@ void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path) for (size_t j = highI -1, k = highI; j > 0; k = j, --j) OffsetPoint(group, path, j, k); - solution->push_back(path_out); + solution->emplace_back(path_out); } void ClipperOffset::DoGroupOffset(Group& group) @@ -441,7 +451,7 @@ void ClipperOffset::DoGroupOffset(Group& group) { // 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 < 0) delta_ = std::abs(delta_); + if (!group.lowest_path_idx.has_value()) delta_ = std::abs(delta_); group_delta_ = (group.is_reversed) ? -delta_ : delta_; } else @@ -454,13 +464,12 @@ void ClipperOffset::DoGroupOffset(Group& group) if (group.join_type == JoinType::Round || group.end_type == EndType::Round) { // calculate the number of steps required to approximate a circle - // (see http://www.angusj.com/clipper2/Docs/Trigonometry.htm) + // (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_) : - std::log10(2 + abs_delta) * default_arc_tolerance); + 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); @@ -491,7 +500,7 @@ void ClipperOffset::DoGroupOffset(Group& group) if (group.join_type == JoinType::Round) { double radius = abs_delta; - int steps = static_cast(std::ceil(steps_per_rad_ * 2 * PI)); //#617 + 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; @@ -507,7 +516,7 @@ void ClipperOffset::DoGroupOffset(Group& group) #endif } - solution->push_back(path_out); + solution->emplace_back(path_out); continue; } // end of offsetting a single point @@ -588,7 +597,7 @@ void ClipperOffset::ExecuteInternal(double delta) if (!solution->size()) return; - bool paths_reversed = CheckReverseOrientation(); + bool paths_reversed = CheckReverseOrientation(); //clean up self-intersections ... Clipper64 c; c.PreserveCollinear(false); @@ -617,10 +626,10 @@ void ClipperOffset::ExecuteInternal(double delta) } } -void ClipperOffset::Execute(double delta, Paths64& paths) +void ClipperOffset::Execute(double delta, Paths64& paths64) { - paths.clear(); - solution = &paths; + paths64.clear(); + solution = &paths64; solution_tree = nullptr; ExecuteInternal(delta); } diff --git a/CPP/Clipper2Lib/src/clipper.rectclip.cpp b/CPP/Clipper2Lib/src/clipper.rectclip.cpp index 3fc6fac2..a5d66df4 100644 --- a/CPP/Clipper2Lib/src/clipper.rectclip.cpp +++ b/CPP/Clipper2Lib/src/clipper.rectclip.cpp @@ -1,13 +1,12 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 27 April 2024 * -* Website : http://www.angusj.com * +* Date : 5 July 2024 * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2024 * * Purpose : FAST rectangular clipping * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ -#include #include "clipper2/clipper.h" #include "clipper2/clipper.rectclip.h" @@ -282,7 +281,7 @@ namespace Clipper2Lib { { if (op->edge) return; op->edge = &edge; - edge.push_back(op); + edge.emplace_back(op); } inline void UncoupleEdge(OutPt2* op) @@ -320,18 +319,19 @@ namespace Clipper2Lib { // 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. - int curr_idx = static_cast(results_.size()) - 1; + size_t curr_idx = results_.size(); OutPt2* result; - if (curr_idx < 0 || start_new) + if (curr_idx == 0 || start_new) { result = &op_container_.emplace_back(OutPt2()); result->pt = pt; result->next = result; result->prev = result; - results_.push_back(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()); @@ -349,27 +349,27 @@ namespace Clipper2Lib { void RectClip64::AddCorner(Location prev, Location curr) { if (HeadingClockwise(prev, curr)) - Add(rect_as_path_[static_cast(prev)]); + Add(rect_as_path_[static_cast(prev)]); else - Add(rect_as_path_[static_cast(curr)]); + Add(rect_as_path_[static_cast(curr)]); } void RectClip64::AddCorner(Location& loc, bool isClockwise) { if (isClockwise) { - Add(rect_as_path_[static_cast(loc)]); + Add(rect_as_path_[static_cast(loc)]); loc = GetAdjacentLocation(loc, true); } else { loc = GetAdjacentLocation(loc, false); - Add(rect_as_path_[static_cast(loc)]); + Add(rect_as_path_[static_cast(loc)]); } } void RectClip64::GetNextLocation(const Path64& path, - Location& loc, int& i, int highI) + Location& loc, size_t& i, size_t highI) { switch (loc) { @@ -423,28 +423,49 @@ namespace Clipper2Lib { } //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) { - int i = 0, highI = static_cast(path.size()) - 1; + 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)) { - i = highI - 1; - while (i >= 0 && !GetLocation(rect_, path[i], prev)) --i; - if (i < 0) + 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; - i = 0; } - Location startingLoc = loc; + Location starting_loc = loc; /////////////////////////////////////////////////// + size_t i = 0; while (i <= highI) { prev = loc; @@ -467,7 +488,7 @@ namespace Clipper2Lib { { bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], rect_mp_); do { - start_locs_.push_back(prev); + start_locs_.emplace_back(prev); prev = GetAdjacentLocation(prev, isClockw); } while (prev != loc); crossing_loc = crossing_prev; // still not crossed @@ -492,7 +513,7 @@ namespace Clipper2Lib { if (first_cross_ == Location::Inside) { first_cross_ = crossing_loc; - start_locs_.push_back(prev); + start_locs_.emplace_back(prev); } else if (prev != crossing_loc) { @@ -514,7 +535,7 @@ namespace Clipper2Lib { if (first_cross_ == Location::Inside) { first_cross_ = loc; - start_locs_.push_back(prev); + start_locs_.emplace_back(prev); } loc = crossing_loc; @@ -543,7 +564,7 @@ namespace Clipper2Lib { if (first_cross_ == Location::Inside) { // path never intersects - if (startingLoc != Location::Inside) + if (starting_loc != Location::Inside) { // path is outside rect // but being outside, it still may not contain rect @@ -552,11 +573,13 @@ namespace Clipper2Lib { { // 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) { - Add(rect_as_path_[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_[j * 2], results_[0]); + AddToEdge(edges_[k * 2], results_[0]); } } } @@ -639,7 +662,7 @@ namespace Clipper2Lib { } } - void RectClip64::TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw) + void RectClip64::TidyEdges(size_t idx, OutPt2List& cw, OutPt2List& ccw) { if (ccw.empty()) return; bool isHorz = ((idx == 1) || (idx == 3)); @@ -726,7 +749,7 @@ namespace Clipper2Lib { if (!isRejoining) { size_t new_idx = results_.size(); - results_.push_back(p1a); + results_.emplace_back(p1a); SetNewOwner(p1a, new_idx); } @@ -837,11 +860,11 @@ namespace Clipper2Lib { if (!op2) return Path64(); Path64 result; - result.push_back(op->pt); + result.emplace_back(op->pt); op2 = op->next; while (op2 != op) { - result.push_back(op2->pt); + result.emplace_back(op2->pt); op2 = op2->next; } return result; @@ -861,20 +884,20 @@ namespace Clipper2Lib { else if (rect_.Contains(path_bounds_)) { // the path must be completely inside rect_ - result.push_back(path); + result.emplace_back(path); continue; } ExecuteInternal(path); CheckEdges(); - for (int i = 0; i < 4; ++i) + 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(tmp); + result.emplace_back(std::move(tmp)); } //clean up after every loop @@ -906,7 +929,7 @@ namespace Clipper2Lib { { Path64 tmp = GetPath(op); if (!tmp.empty()) - result.emplace_back(tmp); + result.emplace_back(std::move(tmp)); } results_.clear(); @@ -924,7 +947,7 @@ namespace Clipper2Lib { op_container_ = std::deque(); start_locs_.clear(); - int i = 1, highI = static_cast(path.size()) - 1; + size_t i = 1, highI = path.size() - 1; Location prev = Location::Inside, loc; Location crossing_loc; @@ -991,11 +1014,11 @@ namespace Clipper2Lib { Path64 result; if (!op || op == op->next) return result; op = op->next; // starting at path beginning - result.push_back(op->pt); + result.emplace_back(op->pt); OutPt2 *op2 = op->next; while (op2 != op) { - result.push_back(op2->pt); + result.emplace_back(op2->pt); op2 = op2->next; } return result; diff --git a/CPP/Examples/Inflate/Inflate.cpp b/CPP/Examples/Inflate/Inflate.cpp index b7a7dcc7..3880c169 100644 --- a/CPP/Examples/Inflate/Inflate.cpp +++ b/CPP/Examples/Inflate/Inflate.cpp @@ -20,71 +20,83 @@ int main(int argc, char* argv[]) //std::getchar(); } -void DoSimpleShapes() +void DoSimpleShapes() { - Paths64 op1, op2; + // OPEN_PATHS SVG: + + PathsD op1, op2; FillRule fr2 = FillRule::EvenOdd; SvgWriter svg2; - - op1.push_back(MakePath({ 80,60, 20,20, 180,20, 180,70, 25,150, 20,180, 180,180 })); + op1.push_back(MakePathD({ 80,60, 20,20, 180,20, 180,70, 25,150, 20,180, 180,180 })); op2 = InflatePaths(op1, 15, JoinType::Miter, EndType::Square, 3); SvgAddOpenSubject(svg2, op1, fr2, false); - SvgAddSolution(svg2, TransformPaths(op2), fr2, false); + SvgAddSolution(svg2, op2, fr2, false); SvgAddCaption(svg2, "Miter Joins; Square Ends", 20, 210); - - op1 = TranslatePaths(op1, 210, 0); + op1 = TranslatePaths(op1, 210, 0); op2 = InflatePaths(op1, 15, JoinType::Square, EndType::Square); SvgAddOpenSubject(svg2, op1, fr2, false); - SvgAddSolution(svg2, TransformPaths(op2), fr2, false); + SvgAddSolution(svg2, op2, fr2, false); SvgAddCaption(svg2, "Square Joins; Square Ends", 230, 210); - - op1 = TranslatePaths(op1, 210, 0); + op1 = TranslatePaths(op1, 210, 0); op2 = InflatePaths(op1, 15, JoinType::Bevel, EndType::Butt, 3); SvgAddOpenSubject(svg2, op1, fr2, false); - SvgAddSolution(svg2, TransformPaths(op2), fr2, false); + SvgAddSolution(svg2, op2, fr2, false); SvgAddCaption(svg2, "Bevel Joins; Butt Ends", 440, 210); - - op1 = TranslatePaths(op1, 210, 0); + op1 = TranslatePaths(op1, 210, 0); op2 = InflatePaths(op1, 15, JoinType::Round, EndType::Round); SvgAddOpenSubject(svg2, op1, fr2, false); - SvgAddSolution(svg2, TransformPaths(op2), fr2, false); + SvgAddSolution(svg2, op2, fr2, false); SvgAddCaption(svg2, "Round Joins; Round Ends", 650, 210); - SvgSaveToFile(svg2, "open_paths.svg", 800, 600, 20); System("open_paths.svg"); - //triangle offset - with large miter - Paths64 p, pp; - p.push_back(MakePath({ 30, 150, 60, 350, 0, 350 })); - pp.insert(pp.end(), p.begin(), p.end()); + // POLYGON JOINTYPES SVG: + // 1. triangle offset - with large miter + int err, scale = 100; + PathsD p, solution; + p.push_back(MakePathD({ 30,150, 60,350, 0,350 })); + solution.insert(solution.end(), p.begin(), p.end()); for (int i = 0; i < 5; ++i) { - //note the relatively large miter limit set here (10) - p = InflatePaths(p, 5, - JoinType::Miter, EndType::Polygon, 10); - pp.insert(pp.end(), p.begin(), p.end()); + p = InflatePaths(p, 5, JoinType::Miter, EndType::Polygon, 10); + solution.insert(solution.end(), p.begin(), p.end()); } + + // 2. open rectangles offset bevelled, squared & rounded ... - //rectangle offset - both squared and rounded p.clear(); - p.push_back(MakePath({ 100,30, 340,30, 340,230, 100,230 })); - pp.insert(pp.end(), p.begin(), p.end()); - //nb: using the ClipperOffest class directly here to control - //different join types within the same offset operation - ClipperOffset co; - co.AddPaths(p, JoinType::Miter, EndType::Joined); - p = TranslatePaths(p, 120, 100); - pp.insert(pp.end(), p.begin(), p.end()); - co.AddPaths(p, JoinType::Round, EndType::Joined); - co.Execute(10, p); - pp.insert(pp.end(), p.begin(), p.end()); - - FillRule fr3 = FillRule::EvenOdd; + p.push_back(MakePathD({ 100,30, 340,30, 340,230, 100,230 })); + p.push_back(TranslatePath(p[0], 60, 50)); + p.push_back(TranslatePath(p[1], 60, 50)); + SvgWriter svg; - SvgAddSolution(svg, TransformPaths(pp), fr3, false); - SvgSaveToFile(svg, "solution_off.svg", 800, 600, 20); - System("solution_off.svg"); + SvgAddOpenSubject(svg, p); + SvgAddCaption(svg, "Bevelled", 100, 15); + SvgAddCaption(svg, "Squared", 160, 65); + SvgAddCaption(svg, "Rounded", 220, 115); + + // nb: we must use the ClipperOffest class directly if we want to + // perform different join types within the same offset operation + // ClipperOffset only supports int64_t coords so, if we want better than unit + // precision, we have to scale manually. (InflatePaths does this scaling internally) + ClipperOffset co; + p = ScalePaths(p, scale, err); + // AddPaths - paths must be int64_t paths + co.AddPath(TransformPath(p[0]), JoinType::Bevel, EndType::Joined); + co.AddPath(TransformPath(p[1]), JoinType::Square, EndType::Joined); + co.AddPath(TransformPath(p[2]), JoinType::Round, EndType::Joined); + Paths64 sol64; + co.Execute(scale * 10, sol64); // ClipperOffset solutions must be int64_t + + // de-scale and append to solution ... + p = ScalePaths(sol64, 1.0 / scale, err); + solution.insert(solution.end(), p.begin(), p.end()); + + string filename = "polygon_jointypes.svg"; + SvgAddSolution(svg, solution, FillRule::EvenOdd, false); + SvgSaveToFile(svg, filename, 800, 600, 20); + System(filename); } void DoRabbit() diff --git a/CPP/Tests/TestExportHeaders.cpp b/CPP/Tests/TestExportHeaders.cpp index f559474b..037ff64f 100644 --- a/CPP/Tests/TestExportHeaders.cpp +++ b/CPP/Tests/TestExportHeaders.cpp @@ -2,7 +2,9 @@ #include "clipper2/clipper.h" #include "clipper2/clipper.core.h" #include "clipper2/clipper.export.h" + using namespace Clipper2Lib; + static bool CreatePolyPath64FromCPolyPath(CPolyPath64& v, PolyPath64& owner) { int64_t poly_len = *v++, child_count = *v++; @@ -12,13 +14,19 @@ static bool CreatePolyPath64FromCPolyPath(CPolyPath64& v, PolyPath64& owner) for (size_t i = 0; i < poly_len; ++i) { int64_t x = *v++, y = *v++; - path.push_back(Point64(x,y)); +#ifdef USINGZ + z_type z = Reinterpret(*v++); + path.push_back(Point64(x, y, z)); +#else + path.push_back(Point64(x, y)); +#endif } PolyPath64* new_owner = owner.AddChild(path); for (size_t i = 0; i < child_count; ++i) CreatePolyPath64FromCPolyPath(v, *new_owner); return true; } + static bool BuildPolyTree64FromCPolyTree(CPolyTree64 tree, PolyTree64& result) { result.Clear(); @@ -28,31 +36,41 @@ static bool BuildPolyTree64FromCPolyTree(CPolyTree64 tree, PolyTree64& result) if (!CreatePolyPath64FromCPolyPath(v, result)) return false; return true; } + static bool CreatePolyPathDFromCPolyPath(CPolyPathD& v, PolyPathD& owner) { - int64_t poly_len = *v++, child_count = *v++; + size_t poly_len = static_cast(*v++); + size_t child_count = static_cast(*v++); if (!poly_len) return false; PathD path; path.reserve(poly_len); for (size_t i = 0; i < poly_len; ++i) { - int64_t x = *v++, y = *v++; + double x = *v++, y = *v++; +#ifdef USINGZ + z_type z = Reinterpret(*v++); + path.push_back(PointD(x, y, z)); +#else path.push_back(PointD(x, y)); +#endif } PolyPathD* new_owner = owner.AddChild(path); for (size_t i = 0; i < child_count; ++i) CreatePolyPathDFromCPolyPath(v, *new_owner); return true; } + static bool BuildPolyTreeDFromCPolyTree(CPolyTreeD tree, PolyTreeD& result) { result.Clear(); double* v = tree; - int64_t array_len = *v++, child_count = *v++; + int64_t array_len = static_cast(*v++); + int64_t child_count = static_cast(*v++); for (size_t i = 0; i < child_count; ++i) if (!CreatePolyPathDFromCPolyPath(v, result)) return false; return true; } + TEST(Clipper2Tests, ExportHeader64) { uint8_t None = 0, Intersection = 1, Union = 2, Difference = 3, Xor = 4; @@ -69,10 +87,10 @@ TEST(Clipper2Tests, ExportHeader64) // Normally clipper.export.h will be compiled into a DLL/so so it can be called // by non C++ applications. If CreateCPaths64 was an exported function and it // was called by a non C++ application, it would crash that application. - CPaths64 c_subj = CreateCPaths(subj); - CPaths64 c_clip = CreateCPaths(clip); + CPaths64 c_subj = CreateCPathsFromPathsT(subj); + CPaths64 c_clip = CreateCPathsFromPathsT(clip); BooleanOp64(Intersection, EvenOdd, c_subj, c_subj_open, c_clip, c_sol, c_sol_open); - solution = ConvertCPaths(c_sol); + solution = ConvertCPathsToPathsT(c_sol); //clean up !!! delete[] c_subj; delete[] c_clip; @@ -80,6 +98,7 @@ TEST(Clipper2Tests, ExportHeader64) DisposeArray64(c_sol_open); EXPECT_EQ(solution.size(), 5); } + TEST(Clipper2Tests, ExportHeaderD) { uint8_t None = 0, Intersection = 1, Union = 2, Difference = 3, Xor = 4; @@ -96,10 +115,10 @@ TEST(Clipper2Tests, ExportHeaderD) // Normally clipper.export.h will be compiled into a DLL/so so it can be called // by non C++ applications. If CreateCPathsD was an exported function and it // was called by a non C++ application, it would crash that application. - CPathsD c_subj = CreateCPaths(subj); - CPathsD c_clip = CreateCPaths(clip); + CPathsD c_subj = CreateCPathsFromPathsT(subj); + CPathsD c_clip = CreateCPathsFromPathsT(clip); BooleanOpD(Intersection, EvenOdd, c_subj, c_subj_open, c_clip, c_sol, c_sol_open); - solution = ConvertCPaths(c_sol); + solution = ConvertCPathsToPathsT(c_sol); //clean up !!! delete[] c_subj; delete[] c_clip; @@ -107,6 +126,7 @@ TEST(Clipper2Tests, ExportHeaderD) DisposeArrayD(c_sol_open); EXPECT_EQ(solution.size(), 5); } + TEST(Clipper2Tests, ExportHeaderTree64) { uint8_t None = 0, Intersection = 1, Union = 2, Difference = 3, Xor = 4; @@ -121,8 +141,8 @@ TEST(Clipper2Tests, ExportHeaderTree64) // More likely, clipper.export.h will be compiled into a DLL/so so it can be // called by non C++ applications. If CreateCPaths64 was an exported function // and it was called by a non C++ application, it would crash that application. - CPaths64 c_subj = CreateCPaths(subj); - CPaths64 c_clip = CreateCPaths(clip); + CPaths64 c_subj = CreateCPathsFromPathsT(subj); + CPaths64 c_clip = CreateCPathsFromPathsT(clip); int64_t* c_sol_tree = nullptr; BooleanOp_PolyTree64(Intersection, EvenOdd, c_subj, c_subj_open, c_clip, c_sol_tree, c_sol_open); PolyTree64 sol_tree; @@ -135,12 +155,15 @@ TEST(Clipper2Tests, ExportHeaderTree64) delete[] c_clip; DisposeArray64(c_sol_tree); DisposeArray64(c_sol_open); + PolyPath64* pp = &sol_tree; - for (int i = 0; i < 4; ++i) + for (int i = 0; i < 5; ++i) { - EXPECT_TRUE(pp->Count() == 1); pp = pp->Child(0); + EXPECT_TRUE(pp->Count() == 1); + pp = pp->Child(0); } } + TEST(Clipper2Tests, ExportHeaderTreeD) { uint8_t None = 0, Intersection = 1, Union = 2, Difference = 3, Xor = 4; @@ -155,11 +178,12 @@ TEST(Clipper2Tests, ExportHeaderTreeD) // More likely, clipper.export.h will be compiled into a DLL/so so it can be // called by non C++ applications. If CreateCPathsD was an exported function // and it was called by a non C++ application, it would crash that application. - CPathsD c_subj = CreateCPaths(subj); - CPathsD c_clip = CreateCPaths(clip); + CPathsD c_subj = CreateCPathsFromPathsT(subj); + CPathsD c_clip = CreateCPathsFromPathsT(clip); static const int precision = 4; CPolyPathD c_sol_tree = nullptr; - BooleanOp_PolyTreeD(Intersection, EvenOdd, c_subj, c_subj_open, c_clip, c_sol_tree, c_sol_open, precision); + BooleanOp_PolyTreeD(Intersection, EvenOdd, c_subj, c_subj_open, c_clip, + c_sol_tree, c_sol_open, precision); PolyTreeD sol_tree; // convert CPolyTreeD to PolyTreeD BuildPolyTreeDFromCPolyTree(c_sol_tree, sol_tree); @@ -171,8 +195,9 @@ TEST(Clipper2Tests, ExportHeaderTreeD) DisposeArrayD(c_sol_tree); DisposeArrayD(c_sol_open); PolyPathD* pp = &sol_tree; - for (int i = 0; i < 4; ++i) + for (int i = 0; i < 5; ++i) { - EXPECT_TRUE(pp->Count() == 1); pp = pp->Child(0); + EXPECT_TRUE(pp->Count() == 1); + pp = pp->Child(0); } } \ No newline at end of file diff --git a/CPP/Tests/TestOffsets.cpp b/CPP/Tests/TestOffsets.cpp index 9fdc15e8..d1af4e74 100644 --- a/CPP/Tests/TestOffsets.cpp +++ b/CPP/Tests/TestOffsets.cpp @@ -12,7 +12,7 @@ TEST(Clipper2Tests, TestOffsets) { ClipperOffset co; Paths64 subject, subject_open, clip; Paths64 solution, solution_open; - ClipType ct = ClipType::None; + ClipType ct = ClipType::NoClip; FillRule fr = FillRule::NonZero; int64_t stored_area = 0, stored_count = 0; ASSERT_TRUE(LoadTestNum(ifs, test_number, subject, subject_open, clip, stored_area, stored_count, ct, fr)); @@ -658,3 +658,20 @@ TEST(Clipper2Tests, TestOffsets10) // see #715 EXPECT_EQ(solution.size(), 2); } +TEST(Clipper2Tests, TestOffsets11) // see #405 +{ + PathsD subject, solution; + subject.push_back(MakePathD({ -1.0, -1.0, -1.0, 11.0, 11.0, 11.0, 11.0, -1.0 })); + // offset polyline + solution = InflatePaths(subject, -50, JoinType::Miter, EndType::Polygon); + EXPECT_TRUE(solution.empty()); +} + +TEST(Clipper2Tests, TestOffsets12) // see #873 +{ + Paths64 subject = { + MakePath({667680768, -36382704, 737202688, -87034880, 742581888, -86055680, 747603968, -84684800}) + }; + Paths64 solution = InflatePaths(subject, -249561088, JoinType::Miter, EndType::Polygon); + EXPECT_TRUE(solution.empty()); +} \ No newline at end of file diff --git a/CPP/Tests/TestPolygons.cpp b/CPP/Tests/TestPolygons.cpp index e2a18c4b..0d9514bf 100644 --- a/CPP/Tests/TestPolygons.cpp +++ b/CPP/Tests/TestPolygons.cpp @@ -58,7 +58,7 @@ TEST(Clipper2Tests, TestMultiplePolygons) if (stored_count <= 0) ; // skip count else if (IsInList(test_number, { 120, 121, 130, 138, - 140, 148, 163, 165, 166, 167, 168, 172, 175, 178, 180 })) + 140, 148, 163, 165, 166, 167, 168, 172, 173, 175, 178, 180 })) EXPECT_NEAR(measured_count, stored_count, 5) << " in test " << test_number; else if (IsInList(test_number, { 27, 181 })) EXPECT_NEAR(measured_count, stored_count, 2) << " in test " << test_number; diff --git a/CPP/Tests/TestPolytreeHoles.cpp b/CPP/Tests/TestPolytreeHoles.cpp index 891094ba..901521a0 100644 --- a/CPP/Tests/TestPolytreeHoles.cpp +++ b/CPP/Tests/TestPolytreeHoles.cpp @@ -9,7 +9,7 @@ TEST(Clipper2Tests, TestPolytreeHoles1) Paths64 subject, subject_open, clip; PolyTree64 solution; Paths64 solution_open; - ClipType ct = ClipType::None; + ClipType ct = ClipType::NoClip; FillRule fr = FillRule::EvenOdd; int64_t area = 0, count = 0; bool success = false; @@ -61,7 +61,7 @@ TEST(Clipper2Tests, TestPolytreeHoles2) ASSERT_TRUE(ifs); ASSERT_TRUE(ifs.good()); Paths64 subject, subject_open, clip; - ClipType ct = ClipType::None; + ClipType ct = ClipType::NoClip; FillRule fr = FillRule::EvenOdd; int64_t area = 0, count = 0; ASSERT_TRUE(LoadTestNum(ifs, 1, subject, subject_open, clip, area, count, ct, fr)); diff --git a/CPP/Tests/TestRect.cpp b/CPP/Tests/TestRect.cpp new file mode 100644 index 00000000..4eefc5fd --- /dev/null +++ b/CPP/Tests/TestRect.cpp @@ -0,0 +1,62 @@ +#include +#include "clipper2/clipper.core.h" + +using namespace Clipper2Lib; + +TEST(Clipper2Tests, TestRectOpPlus) +{ + { + Rect64 lhs = Rect64::InvalidRect(); + Rect64 rhs(-1, -1, 10, 10); + { + Rect64 sum = lhs + rhs; + EXPECT_EQ(rhs, sum); + } + { + std::swap(lhs, rhs); + Rect64 sum = lhs + rhs; + EXPECT_EQ(lhs, sum); + } + } + { + Rect64 lhs = Rect64::InvalidRect(); + Rect64 rhs(1, 1, 10, 10); + { + Rect64 sum = lhs + rhs; + EXPECT_EQ(rhs, sum); + } + { + std::swap(lhs, rhs); + Rect64 sum = lhs + rhs; + EXPECT_EQ(lhs, sum); + } + } + { + Rect64 lhs(0, 0, 1, 1); + Rect64 rhs(-1, -1, 0, 0); + Rect64 expected(-1, -1, 1, 1); + { + Rect64 sum = lhs + rhs; + EXPECT_EQ(expected, sum); + } + { + std::swap(lhs, rhs); + Rect64 sum = lhs + rhs; + EXPECT_EQ(expected, sum); + } + } + { + Rect64 lhs(-10, -10, -1, -1); + Rect64 rhs(1, 1, 10, 10); + Rect64 expected(-10, -10, 10, 10); + { + Rect64 sum = lhs + rhs; + EXPECT_EQ(expected, sum); + } + { + std::swap(lhs, rhs); + Rect64 sum = lhs + rhs; + EXPECT_EQ(expected, sum); + } + } +} \ No newline at end of file diff --git a/CPP/Tests/TestRectClip.cpp b/CPP/Tests/TestRectClip.cpp index c320a9bc..4b8972a0 100644 --- a/CPP/Tests/TestRectClip.cpp +++ b/CPP/Tests/TestRectClip.cpp @@ -63,4 +63,14 @@ TEST(Clipper2Tests, TestRectClip3) //#637 solution = RectClip(r, subject); //std::cout << solution << std::endl; EXPECT_TRUE(solution.size() == 1); +} + +TEST(Clipper2Tests, TestRectClipOrientation) //#864 +{ + const Rect64 rect(1222, 1323, 3247, 3348); + const Path64 subject = MakePath({ 375,1680, 1915,4716, 5943,586, 3987,152 }); + RectClip64 clip(rect); + const auto solution = clip.Execute({ subject }); + ASSERT_EQ(solution.size(), 1); + EXPECT_EQ(IsPositive(subject), IsPositive(solution.front())); } \ No newline at end of file diff --git a/CPP/Utils/ClipFileSave.cpp b/CPP/Utils/ClipFileSave.cpp index 305484c6..f7dfc70b 100644 --- a/CPP/Utils/ClipFileSave.cpp +++ b/CPP/Utils/ClipFileSave.cpp @@ -14,7 +14,7 @@ namespace Clipper2Lib { // Boyer Moore Horspool Search //------------------------------------------------------------------------------ -class BMH_Search +class BMH_Search { private: uint8_t case_table[256]; @@ -51,7 +51,7 @@ class BMH_Search } void Init() - { + { case_sensitive_ = false; current = nullptr; end = nullptr; last_found = nullptr; } @@ -85,7 +85,7 @@ class BMH_Search while (current < end) { uint8_t i = shift[case_table[static_cast(*current)]]; - if (!i) + if (!i) { char* j = current - needle_len_less1; while (i < needle_len_less1 && @@ -107,7 +107,7 @@ class BMH_Search public: - explicit BMH_Search(std::ifstream& stream, + explicit BMH_Search(std::ifstream& stream, const std::string& needle = "") { //case blind table @@ -121,7 +121,7 @@ class BMH_Search if (needle.size() > 0) SetNeedle(needle); } - BMH_Search(const char* haystack, size_t length, + BMH_Search(const char* haystack, size_t length, const std::string& needle = "") { Init(); @@ -137,7 +137,7 @@ class BMH_Search void Reset() { - current = haystack_; + current = haystack_; last_found = nullptr; } @@ -152,7 +152,7 @@ class BMH_Search needle_.clear(); needle_.reserve(needle_len_); for (const char& c : needle) needle_.push_back(static_cast(c)); - + //case insensitive needle needle_ic_ = needle_; for (std::vector< uint8_t>::iterator ui = needle_ic_.begin(); ui != needle_ic_.end(); ++ui) @@ -199,10 +199,10 @@ class BMH_Search inline size_t LastFoundOffset() { return last_found - haystack_; } inline char* FindNextEndLine() - { + { current = last_found + needle_len_; - while (current < end && - *current != char(13) && *current != char(10)) + while (current < end && + *current != char(13) && *current != char(10)) ++current; return current; } @@ -212,7 +212,7 @@ class BMH_Search void PathsToStream(const Paths64& paths, std::ostream& stream) { - for (Paths64::const_iterator paths_it = paths.cbegin(); + for (Paths64::const_iterator paths_it = paths.cbegin(); paths_it != paths.cend(); ++paths_it) { //watch out for empty paths @@ -247,14 +247,14 @@ static bool GetInt(string::const_iterator& s_it, } bool SaveTest(const std::string& filename, bool append, - const Paths64* subj, const Paths64* subj_open, const Paths64* clip, + const Paths64* subj, const Paths64* subj_open, const Paths64* clip, int64_t area, int64_t count, ClipType ct, FillRule fr) { string line; bool found = false; int last_cap_pos = 0, curr_cap_pos = 0; int64_t last_test_no = 0; - if (append && FileExists(filename)) //get the number of the preceeding test + if (append && FileExists(filename)) //get the number of the preceding test { ifstream file; file.open(filename, std::ios::binary); @@ -267,8 +267,8 @@ bool SaveTest(const std::string& filename, bool append, string::const_iterator s_it = line.cbegin(), s_end = line.cend(); GetInt(s_it, s_end, last_test_no); } - } - else if (FileExists(filename)) + } + else if (FileExists(filename)) remove(filename.c_str()); ++last_test_no; @@ -282,7 +282,7 @@ bool SaveTest(const std::string& filename, bool append, string cliptype_string; switch (ct) { - case ClipType::None: cliptype_string = "NONE"; break; + case ClipType::NoClip: cliptype_string = "NOCLIP"; break; case ClipType::Intersection: cliptype_string = "INTERSECTION"; break; case ClipType::Union: cliptype_string = "UNION"; break; case ClipType::Difference: cliptype_string = "DIFFERENCE"; break; diff --git a/CPP/Utils/clipper.svg.cpp b/CPP/Utils/clipper.svg.cpp index 1206bbb1..ab6f8be3 100644 --- a/CPP/Utils/clipper.svg.cpp +++ b/CPP/Utils/clipper.svg.cpp @@ -1,9 +1,9 @@ /******************************************************************************* * Author : Angus Johnson * * Date : 24 March 2024 * -* Website : http://www.angusj.com * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2024 * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #include diff --git a/CPP/Utils/clipper.svg.h b/CPP/Utils/clipper.svg.h index 2c718137..1abf61bf 100644 --- a/CPP/Utils/clipper.svg.h +++ b/CPP/Utils/clipper.svg.h @@ -1,9 +1,9 @@ /******************************************************************************* * Author : Angus Johnson * * Date : 24 March 2024 * -* Website : http://www.angusj.com * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2024 * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #ifndef svglib_h diff --git a/CPP/Utils/clipper.svg.utils.h b/CPP/Utils/clipper.svg.utils.h index dbfe5c55..2764b3b9 100644 --- a/CPP/Utils/clipper.svg.utils.h +++ b/CPP/Utils/clipper.svg.utils.h @@ -1,9 +1,9 @@ /******************************************************************************* * Author : Angus Johnson * * Date : 16 June 2022 * -* Website : http://www.angusj.com * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2022 * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #ifndef svgutillib_h diff --git a/CSharp/Clipper2Lib.Benchmark/Benchmarks.cs b/CSharp/Clipper2Lib.Benchmark/Benchmarks.cs index ed1ade11..367586ae 100644 --- a/CSharp/Clipper2Lib.Benchmark/Benchmarks.cs +++ b/CSharp/Clipper2Lib.Benchmark/Benchmarks.cs @@ -36,9 +36,9 @@ public void GlobalSetup() { Random rand = new (); - _subj = new (); - _clip = new (); - _solution = new (); + _subj = new Paths64(); + _clip = new Paths64(); + _solution = new Paths64(); _subj.Add(MakeRandomPath(DisplayWidth, DisplayHeight, EdgeCount, rand)); _clip.Add(MakeRandomPath(DisplayWidth, DisplayHeight, EdgeCount, rand)); diff --git a/CSharp/Clipper2Lib.Benchmark/Program.cs b/CSharp/Clipper2Lib.Benchmark/Program.cs index 116e4a38..fc54c375 100644 --- a/CSharp/Clipper2Lib.Benchmark/Program.cs +++ b/CSharp/Clipper2Lib.Benchmark/Program.cs @@ -2,7 +2,7 @@ namespace Drecom.Clipper2Lib.Benchmark { - public class Program + public static class Program { public static void Main() { diff --git a/CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs b/CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs index 33fd63da..94aa277c 100644 --- a/CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs +++ b/CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs @@ -1,9 +1,9 @@ /******************************************************************************* * Author : Angus Johnson * * Date : 1 January 2023 * -* Website : http://www.angusj.com * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ using System.Reflection; @@ -15,7 +15,7 @@ namespace ClipperDemo1 { - public class Application + public static class Application { public static void Main() { @@ -34,7 +34,7 @@ public static Paths64 Polytree_Union(Paths64 subjects, FillRule fillrule) { // of course this function is inefficient, // but it's purpose is simply to test polytrees. - PolyTree64 polytree = new PolyTree64(); + PolyTree64 polytree = new(); Clipper.BooleanOp(ClipType.Union, subjects, null, polytree, fillrule); return Clipper.PolyTreeToPaths64(polytree); } @@ -43,7 +43,7 @@ public static void SquaresTest(bool test_polytree = false) { const int size = 10; const int w = 800, h = 600; - FillRule fillrule = FillRule.NonZero; + const FillRule fillrule = FillRule.NonZero; Path64 shape = Clipper.MakePath(new int[] { 0, 0, size, 0, size, size, 0, size }); Paths64 subjects = new(), solution; @@ -63,10 +63,10 @@ public static void SquaresTest(bool test_polytree = false) else solution = Clipper.Union(subjects, fillrule); - SvgWriter svg = new SvgWriter(); + SvgWriter svg = new(); SvgUtils.AddSubject(svg, subjects); SvgUtils.AddSolution(svg, solution, false); - string filename = @"..\..\..\squares.svg"; + const string filename = @"..\..\..\squares.svg"; SvgUtils.SaveToFile(svg, filename, fillrule, w, h, 10); ClipperFileIO.OpenFileWithDefaultApp(filename); } @@ -75,7 +75,7 @@ public static void TrianglesTest(bool test_polytree = false) { const int size = 10; const int w = 800, h = 600; - FillRule fillrule = FillRule.NonZero; + const FillRule fillrule = FillRule.NonZero; Path64 tri1 = Clipper.MakePath(new int[] { 0,0, size * 2,0, size,size * 2 }); Path64 tri2 = Clipper.MakePath(new int[] { size * 2, 0, size, size * 2, size*3, size*2 }); @@ -100,10 +100,10 @@ public static void TrianglesTest(bool test_polytree = false) else solution = Clipper.Union(subjects, fillrule); - SvgWriter svg = new SvgWriter(); + SvgWriter svg = new(); SvgUtils.AddSubject(svg, subjects); SvgUtils.AddSolution(svg, solution, false); - string filename = @"..\..\..\triangles.svg"; + const string filename = @"..\..\..\triangles.svg"; SvgUtils.SaveToFile(svg, filename, fillrule, w, h, 10); ClipperFileIO.OpenFileWithDefaultApp(filename); } @@ -112,7 +112,7 @@ public static void DiamondsTest(bool test_polytree = false) { const int size = 10; const int w = 800, h = 600; - FillRule fillrule = FillRule.NonZero; + const FillRule fillrule = FillRule.NonZero; Path64 shape = Clipper.MakePath(new int[] { size, 0, size * 2, size, size, size * 2, 0, size }); Paths64 subjects = new(), solution; @@ -135,36 +135,34 @@ public static void DiamondsTest(bool test_polytree = false) else solution = Clipper.Union(subjects, fillrule); - SvgWriter svg = new SvgWriter(); + SvgWriter svg = new(); SvgUtils.AddSubject(svg, subjects); SvgUtils.AddSolution(svg, solution, false); - string filename = @"..\..\..\diamonds.svg"; + const string filename = @"..\..\..\diamonds.svg"; SvgUtils.SaveToFile(svg, filename, fillrule, w, h, 10); ClipperFileIO.OpenFileWithDefaultApp(filename); } public static void LoopThruTestPolygons(int start = 0, int end = 0) { - Paths64 subject = new Paths64(); - Paths64 subject_open = new Paths64(); - Paths64 clip = new Paths64(); - Paths64 solution = new Paths64(); - Paths64 solution_open = new Paths64(); - ClipType ct; - FillRule fr; + Paths64 subject = new(); + Paths64 subject_open = new(); + Paths64 clip = new(); + Paths64 solution = new(); + Paths64 solution_open = new(); bool do_all = (start == 0 && end == 0); if (do_all) { start = 1; end = 0xFFFF; } else if (end == 0) end = start; if (do_all) Console.WriteLine("\nCount and area differences (expected vs measured):\n"); - int test_number = start; + int test_number = start; for (; test_number <= end; ++test_number) { if (!ClipperFileIO.LoadTestNum(@"..\..\..\..\..\..\Tests\Polygons.txt", - test_number, subject, subject_open, clip, - out ct, out fr, out long area, out int cnt, out _)) break; - Clipper64 c64 = new Clipper64(); + test_number, subject, subject_open, clip, + out ClipType ct, out FillRule fr, out long area, out int cnt, out _)) break; + Clipper64 c64 = new(); c64.AddSubject(subject); c64.AddOpenSubject(subject_open); c64.AddClip(clip); @@ -181,15 +179,15 @@ public static void LoopThruTestPolygons(int start = 0, int end = 0) double area_diff = area <= 0 ? 0 : Math.Abs(measuredArea / area -1.0); if (count_diff > 0.05) - Console.WriteLine(string.Format("{0}: count {1} vs {2}", test_number, cnt, measuredCnt)); + Console.WriteLine($"{test_number}: count {cnt} vs {measuredCnt}"); if (area_diff > 0.1) - Console.WriteLine(string.Format("{0}: area {1} vs {2}", test_number, area, measuredArea)); + Console.WriteLine($"{test_number}: area {area} vs {measuredArea}"); // don't display when looping through every test continue; } - SvgWriter svg = new SvgWriter(); + SvgWriter svg = new(); SvgUtils.AddSubject(svg, subject); SvgUtils.AddClip(svg, clip); if (fr == FillRule.Negative) @@ -201,11 +199,9 @@ public static void LoopThruTestPolygons(int start = 0, int end = 0) ClipperFileIO.OpenFileWithDefaultApp(filename); } - if (do_all) - { - Console.WriteLine(string.Format("\ntest ended at polygon {0}.\n", test_number)); - Console.ReadKey(); - } + if (!do_all) return; + Console.WriteLine($"\ntest ended at polygon {test_number}.\n"); + Console.ReadKey(); } public static Paths64 LoadPathsFromResource(string resourceName) @@ -233,7 +229,7 @@ public static Paths64 LoadPathsFromResource(string resourceName) public static void ClipTestPolys() { - FillRule fillrule = FillRule.NonZero; + const FillRule fillrule = FillRule.NonZero; Paths64 subject = LoadPathsFromResource("ConsoleDemo.subj.bin"); Paths64 clip = LoadPathsFromResource("ConsoleDemo.clip.bin"); Paths64 solution = Clipper.Intersect(subject, clip, fillrule); @@ -242,8 +238,8 @@ public static void ClipTestPolys() SvgUtils.AddSubject(svg, subject); SvgUtils.AddClip(svg, clip); SvgUtils.AddSolution(svg, solution, false); - SvgUtils.SaveToFile(svg, "..\\..\\..\\clipperD.svg", fillrule, 800, 600, 20); - ClipperFileIO.OpenFileWithDefaultApp("..\\..\\..\\clipperD.svg"); + SvgUtils.SaveToFile(svg, @"..\..\..\clipperD.svg", fillrule, 800, 600, 20); + ClipperFileIO.OpenFileWithDefaultApp(@"..\..\..\clipperD.svg"); } diff --git a/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs b/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs index bdb30fed..278cf737 100644 --- a/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs +++ b/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs @@ -1,12 +1,11 @@ /******************************************************************************* * Author : Angus Johnson * * Date : 24 September 2023 * -* Website : http://www.angusj.com * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ -using System; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; @@ -15,7 +14,7 @@ namespace ClipperDemo1 { - public class Application + public static class Application { public static void Main() @@ -28,42 +27,48 @@ public static void Main() public static void DoSimpleShapes() { SvgWriter svg = new(); - ClipperOffset co = new(); - //triangle offset - with large miter - Paths64 p0 = new() { Clipper.MakePath(new int[] { 30,150, 60,350, 0,350 }) }; - Paths64 p = new(); + //TRIANGLE OFFSET - WITH LARGE MITER + + PathsD pp = new() { Clipper.MakePath(new double[] { 30,150, 60,350, 0,350 }) }; + PathsD solution = new(); for (int i = 0; i < 5; ++i) { //nb: the last parameter here (10) greatly increases miter limit - p0 = Clipper.InflatePaths(p0, 5, JoinType.Miter, EndType.Polygon, 10); - p.AddRange(p0); + pp = Clipper.InflatePaths(pp, 5, JoinType.Miter, EndType.Polygon, 10); + solution.AddRange(pp); } - SvgUtils.AddSolution(svg, p, false); - p.Clear(); + SvgUtils.AddSolution(svg, solution, false); - //rectangle offset - both squared and rounded - //nb: using the ClipperOffest class directly here to control - //different join types within the same offset operation - p.Add(Clipper.MakePath(new int[] { 100,0, 340,0, 340,200, 100,200, 100, 0 })); - SvgUtils.AddOpenSubject(svg, p); - co.AddPaths(p, JoinType.Bevel, EndType.Joined); + // RECTANGLE OFFSET - BEVEL, SQUARED AND ROUNDED - p = Clipper.TranslatePaths(p, 60, 50); - SvgUtils.AddOpenSubject(svg, p); - co.AddPaths(p, JoinType.Square, EndType.Joined); - p = Clipper.TranslatePaths(p, 60, 50); - SvgUtils.AddOpenSubject(svg, p); - co.AddPaths(p, JoinType.Round, EndType.Joined); - - co.Execute(10, p); + solution.Clear(); + solution.Add(Clipper.MakePath(new double[] { 100, 0, 340, 0, 340, 200, 100, 200 })); + solution.Add(Clipper.TranslatePath(solution[0], 60, 50)); + solution.Add(Clipper.TranslatePath(solution[1], 60, 50)); + SvgUtils.AddOpenSubject(svg, solution); - string filename = "../../../inflate.svg"; - SvgUtils.AddSolution(svg, p, false); - SvgUtils.AddCaption(svg, "Beveled join", 100, -27); - SvgUtils.AddCaption(svg, "Squared join", 160, 23); - SvgUtils.AddCaption(svg, "Rounded join", 220, 73); - SvgUtils.SaveToFile(svg, filename, FillRule.EvenOdd, 800, 600, 20); + // nb: rather than using InflatePaths(), we have to use the + // ClipperOffest class directly because we want to perform + // different join types in a single offset operation + ClipperOffset co = new(); + // because ClipperOffset only accepts Int64 paths, scale them + // so the de-scaled offset result will have greater precision + double scale = 100; + Paths64 pp64 = Clipper.ScalePaths64(solution, scale); + co.AddPath(pp64[0], JoinType.Bevel, EndType.Joined); + co.AddPath(pp64[1], JoinType.Square, EndType.Joined); + co.AddPath(pp64[2], JoinType.Round, EndType.Joined); + co.Execute(10 * scale, pp64); + // now de-scale the offset solution + solution = Clipper.ScalePathsD(pp64, 1 / scale); + + const string filename = "../../../inflate.svg"; + SvgUtils.AddSolution(svg, solution, false); + SvgUtils.AddCaption(svg, "Beveled join", 100, -17); + SvgUtils.AddCaption(svg, "Squared join", 160, 33); + SvgUtils.AddCaption(svg, "Rounded join", 220, 83); + SvgUtils.SaveToFile(svg, filename, FillRule.EvenOdd, 800, 600, 40); ClipperFileIO.OpenFileWithDefaultApp(filename); } @@ -82,7 +87,7 @@ public static void DoRabbit() solution.AddRange(pd); } - string filename = "../../../rabbit.svg"; + const string filename = "../../../rabbit.svg"; SvgWriter svg = new (); SvgUtils.AddSolution(svg, solution, false); SvgUtils.SaveToFile(svg, filename, FillRule.EvenOdd, 450, 720, 10); @@ -119,10 +124,9 @@ public static void DoVariableOffset() ClipperOffset co = new(); co.AddPaths(p, JoinType.Square, EndType.Butt); co.Execute( - delegate (Path64 path, PathD path_norms, int currPt, int prevPt) - { return currPt* currPt + 10; } , solution); + (path, path_norms, currPt, prevPt) => currPt * currPt + 10, solution); - string filename = "../../../variable_offset.svg"; + const string filename = "../../../variable_offset.svg"; SvgWriter svg = new(); SvgUtils.AddOpenSubject(svg, p); SvgUtils.AddSolution(svg, solution, true); diff --git a/CSharp/Clipper2Lib.Examples/RectClip/Main.cs b/CSharp/Clipper2Lib.Examples/RectClip/Main.cs index d8b7748d..b3b5030d 100644 --- a/CSharp/Clipper2Lib.Examples/RectClip/Main.cs +++ b/CSharp/Clipper2Lib.Examples/RectClip/Main.cs @@ -1,9 +1,9 @@ /******************************************************************************* * Author : Angus Johnson * * Date : 11 February 2023 * -* Website : http://www.angusj.com * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ using System; diff --git a/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestOffset.cs b/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestOffset.cs index 54522c74..cd9bfef0 100644 --- a/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestOffset.cs +++ b/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestOffset.cs @@ -1,6 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Clipper2Lib.UnitTests +namespace Drecom.Clipper2Lib.UnitTests { [TestClass] diff --git a/CSharp/Clipper2Lib/Clipper.Core.cs b/CSharp/Clipper2Lib/Clipper.Core.cs index 9fa1a4a6..f22ad461 100644 --- a/CSharp/Clipper2Lib/Clipper.Core.cs +++ b/CSharp/Clipper2Lib/Clipper.Core.cs @@ -1,10 +1,10 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 13 May 2024 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2024 * +* Date : 22 January 2025 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2025 * * Purpose : Core structures and functions for the Clipper Library * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #nullable enable @@ -21,47 +21,66 @@ public struct Point64 #if USINGZ public long Z; +#endif public Point64(Point64 pt) { X = pt.X; Y = pt.Y; +#if USINGZ Z = pt.Z; +#endif } public Point64(Point64 pt, double scale) { X = (long) Math.Round(pt.X * scale, MidpointRounding.AwayFromZero); Y = (long) Math.Round(pt.Y * scale, MidpointRounding.AwayFromZero); +#if USINGZ Z = (long) Math.Round(pt.Z * scale, MidpointRounding.AwayFromZero); +#endif } - public Point64(long x, long y, long z = 0) - { + public Point64(long x, long y +#if USINGZ + , long z = 0 +#endif + ) { X = x; Y = y; +#if USINGZ Z = z; +#endif } - public Point64(double x, double y, double z = 0.0) - { + public Point64(double x, double y +#if USINGZ + , double z = 0.0 +#endif + ) { X = (long) Math.Round(x, MidpointRounding.AwayFromZero); Y = (long) Math.Round(y, MidpointRounding.AwayFromZero); +#if USINGZ Z = (long) Math.Round(z, MidpointRounding.AwayFromZero); +#endif } public Point64(PointD pt) { X = (long) Math.Round(pt.x, MidpointRounding.AwayFromZero); Y = (long) Math.Round(pt.y, MidpointRounding.AwayFromZero); +#if USINGZ Z = pt.z; +#endif } public Point64(PointD pt, double scale) { X = (long) Math.Round(pt.x * scale, MidpointRounding.AwayFromZero); Y = (long) Math.Round(pt.y * scale, MidpointRounding.AwayFromZero); +#if USINGZ Z = pt.z; +#endif } public static bool operator ==(Point64 lhs, Point64 rhs) @@ -76,81 +95,33 @@ public Point64(PointD pt, double scale) public static Point64 operator +(Point64 lhs, Point64 rhs) { - return new Point64(lhs.X + rhs.X, lhs.Y + rhs.Y, lhs.Z + rhs.Z); + return new Point64(lhs.X + rhs.X, lhs.Y + rhs.Y +#if USINGZ + , lhs.Z + rhs.Z +#endif + ); } public static Point64 operator -(Point64 lhs, Point64 rhs) { - return new Point64(lhs.X - rhs.X, lhs.Y - rhs.Y, lhs.Z - rhs.Z); + return new Point64(lhs.X - rhs.X, lhs.Y - rhs.Y +#if USINGZ + , lhs.Z - rhs.Z +#endif + ); } public readonly override string ToString() { - return $"{X},{Y},{Z} "; // nb: trailing space - } - + // nb: trailing space +#if USINGZ + return $"{X},{Y},{Z} "; #else - public Point64(Point64 pt) - { - X = pt.X; - Y = pt.Y; - } - - public Point64(long x, long y) - { - X = x; - Y = y; - } - - public Point64(double x, double y) - { - X = (long) Math.Round(x, MidpointRounding.AwayFromZero); - Y = (long) Math.Round(y, MidpointRounding.AwayFromZero); - } - - public Point64(PointD pt) - { - X = (long) Math.Round(pt.x, MidpointRounding.AwayFromZero); - Y = (long) Math.Round(pt.y, MidpointRounding.AwayFromZero); - } - - public Point64(Point64 pt, double scale) - { - X = (long) Math.Round(pt.X * scale, MidpointRounding.AwayFromZero); - Y = (long) Math.Round(pt.Y * scale, MidpointRounding.AwayFromZero); - } - - public Point64(PointD pt, double scale) - { - X = (long) Math.Round(pt.x * scale, MidpointRounding.AwayFromZero); - Y = (long) Math.Round(pt.y * scale, MidpointRounding.AwayFromZero); - } - - public static bool operator ==(Point64 lhs, Point64 rhs) - { - return lhs.X == rhs.X && lhs.Y == rhs.Y; - } - - public static bool operator !=(Point64 lhs, Point64 rhs) - { - return lhs.X != rhs.X || lhs.Y != rhs.Y; - } + return $"{X},{Y} "; +#endif - public static Point64 operator +(Point64 lhs, Point64 rhs) - { - return new Point64(lhs.X + rhs.X, lhs.Y + rhs.Y); } - public static Point64 operator -(Point64 lhs, Point64 rhs) - { - return new Point64(lhs.X - rhs.X, lhs.Y - rhs.Y); - } - public readonly override string ToString() - { - return $"{X},{Y} "; // nb: trailing space - } - -#endif public readonly override bool Equals(object? obj) { if (obj != null && obj is Point64 p) @@ -172,97 +143,77 @@ public struct PointD #if USINGZ public long z; +#endif public PointD(PointD pt) { x = pt.x; y = pt.y; +#if USINGZ z = pt.z; +#endif } public PointD(Point64 pt) { x = pt.X; y = pt.Y; +#if USINGZ z = pt.Z; +#endif } public PointD(Point64 pt, double scale) { x = pt.X * scale; y = pt.Y * scale; +#if USINGZ z = pt.Z; +#endif } public PointD(PointD pt, double scale) { x = pt.x * scale; y = pt.y * scale; +#if USINGZ z = pt.z; +#endif } - public PointD(long x, long y, long z = 0) - { + public PointD(long x, long y +#if USINGZ + , long z = 0 +#endif + ) { this.x = x; this.y = y; +#if USINGZ this.z = z; +#endif } - public PointD(double x, double y, long z = 0) - { + public PointD(double x, double y +#if USINGZ + , long z = 0 +#endif + ) { this.x = x; this.y = y; +#if USINGZ this.z = z; +#endif } public readonly string ToString(int precision = 2) { +#if USINGZ return string.Format($"{{0:F{precision}}},{{1:F{precision}}},{{2:D}}", x,y,z); - } - #else - public PointD(PointD pt) - { - x = pt.x; - y = pt.y; - } - - public PointD(Point64 pt) - { - x = pt.X; - y = pt.Y; - } - - public PointD(PointD pt, double scale) - { - x = pt.x * scale; - y = pt.y * scale; - } - - public PointD(Point64 pt, double scale) - { - x = pt.X * scale; - y = pt.Y * scale; - } - - public PointD(long x, long y) - { - this.x = x; - this.y = y; - } - - public PointD(double x, double y) - { - this.x = x; - this.y = y; - } - - public readonly string ToString(int precision = 2) - { return string.Format($"{{0:F{precision}}},{{1:F{precision}}}", x,y); +#endif } -#endif public static bool operator ==(PointD lhs, PointD rhs) { return InternalClipper.IsAlmostZero(lhs.x - rhs.x) && @@ -532,18 +483,18 @@ public string ToString(int precision = 2) // Note: all clipping operations except for Difference are commutative. public enum ClipType { - None, + NoClip, Intersection, Union, Difference, Xor - }; + } public enum PathType { Subject, Clip - }; + } // By far the most widely used filling rules for polygons are EvenOdd // and NonZero, sometimes called Alternate and Winding respectively. @@ -554,7 +505,7 @@ public enum FillRule NonZero, Positive, Negative - }; + } // PointInPolygon internal enum PipResult @@ -562,7 +513,7 @@ internal enum PipResult Inside, Outside, OnEdge - }; + } public static class InternalClipper { @@ -572,7 +523,6 @@ public static class InternalClipper internal const double min_coord = -MaxCoord; internal const long Invalid64 = MaxInt64; - internal const double defaultArcTolerance = 0.25; internal const double floatingPointTolerance = 1E-12; internal const double defaultMinimumEdgeLength = 0.1; @@ -612,8 +562,7 @@ internal static bool IsAlmostZero(double value) internal static int TriSign(long x) // returns 0, 1 or -1 { if (x < 0) return -1; - else if (x > 1) return 1; - else return 0; + return x > 1 ? 1 : 0; } public struct MultiplyUInt64Result @@ -725,24 +674,19 @@ public static bool GetSegmentIntersectPt(Point64 ln1a, internal static bool SegsIntersect(Point64 seg1a, Point64 seg1b, Point64 seg2a, 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; - // ensure NOT collinear - return (res1 != 0 || res2 != 0 || res3 != 0 || res4 != 0); - } - else - { - return (CrossProduct(seg1a, seg2a, seg2b) * - CrossProduct(seg1b, seg2a, seg2b) < 0) && - (CrossProduct(seg2a, seg1a, seg1b) * - CrossProduct(seg2b, seg1a, seg1b) < 0); - } + if (!inclusive) + return (CrossProduct(seg1a, seg2a, seg2b) * + CrossProduct(seg1b, seg2a, seg2b) < 0) && + (CrossProduct(seg2a, seg1a, seg1b) * + CrossProduct(seg2b, seg1a, seg1b) < 0); + 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; + // ensure NOT collinear + return (res1 != 0 || res2 != 0 || res3 != 0 || res4 != 0); } public static Point64 GetClosestPtOnSegment(Point64 offPt, Point64 seg1, Point64 seg2) @@ -783,14 +727,14 @@ public static PointInPolygonResult PointInPolygon(Point64 pt, Path64 polygon) if (isAbove) { while (i < end && polygon[i].Y < pt.Y) i++; - if (i == end) continue; } else { while (i < end && polygon[i].Y > pt.Y) i++; - if (i == end) continue; } + if (i == end) continue; + Point64 curr = polygon[i], prev; if (i > 0) prev = polygon[i - 1]; else prev = polygon[len - 1]; @@ -823,20 +767,13 @@ public static PointInPolygonResult PointInPolygon(Point64 pt, Path64 polygon) i++; } - if (isAbove != startingAbove) - { - if (i == len) i = 0; - if (i == 0) - d = CrossProduct(polygon[len - 1], polygon[0], pt); - else - d = CrossProduct(polygon[i - 1], polygon[i], pt); - if (d == 0) return PointInPolygonResult.IsOn; - if ((d < 0) == isAbove) val = 1 - val; - } + if (isAbove == startingAbove) return val == 0 ? PointInPolygonResult.IsOutside : PointInPolygonResult.IsInside; + if (i == len) i = 0; + d = i == 0 ? CrossProduct(polygon[len - 1], polygon[0], pt) : CrossProduct(polygon[i - 1], polygon[i], pt); + if (d == 0) return PointInPolygonResult.IsOn; + if ((d < 0) == isAbove) val = 1 - val; - if (val == 0) - return PointInPolygonResult.IsOutside; - return PointInPolygonResult.IsInside; + return val == 0 ? PointInPolygonResult.IsOutside : PointInPolygonResult.IsInside; } } // InternalClipper diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index a4542199..d685da8e 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,12 +1,12 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 27 April 2024 * -* Website : http://www.angusj.com * +* Date : 10 October 2024 * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2024 * * Purpose : This is the main polygon clipping module * * Thanks : Special thanks to Thong Nguyen, Guus Kuiper, Phil Stopford, * * : and Daniel Gosnell for their invaluable assistance with C#. * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #nullable enable @@ -27,7 +27,7 @@ public enum PointInPolygonResult IsOn = 0, IsInside = 1, IsOutside = 2 - }; + } [Flags] internal enum VertexFlags @@ -37,7 +37,7 @@ internal enum VertexFlags OpenEnd = 2, LocalMax = 4, LocalMin = 8 - }; + } internal class Vertex { @@ -53,7 +53,7 @@ public Vertex(Point64 pt, VertexFlags flags, Vertex? prev) next = null; this.prev = prev; } - }; + } internal readonly struct LocalMinima { @@ -87,7 +87,7 @@ public override int GetHashCode() { return vertex.GetHashCode(); } - }; + } // IntersectNode: a structure representing 2 intersecting edges. // Intersections must be sorted so they are processed from the largest @@ -104,7 +104,7 @@ public IntersectNode(Point64 pt, Active edge1, Active edge2) this.edge1 = edge1; this.edge2 = edge2; } - }; + } internal struct LocMinSorter : IComparer { @@ -131,10 +131,10 @@ public OutPt(Point64 pt, OutRec outrec) prev = this; horz = null; } - }; + } - internal enum JoinWith { None, Left, Right }; - internal enum HorzPosition { Bottom, Middle, Top }; + internal enum JoinWith { None, Left, Right } + internal enum HorzPosition { Bottom, Middle, Top } // OutRec: path data structure for clipping solutions @@ -151,7 +151,7 @@ internal class OutRec public bool isOpen; public List? splits; public OutRec? recursiveSplit; - }; + } internal class HorzSegment { @@ -210,7 +210,7 @@ internal class Active public LocalMinima localMin; // the bottom of an edge 'bound' (also Vatti) internal bool isLeftBound; internal JoinWith joinWith; - }; + } internal static class ClipperEngine { @@ -258,14 +258,14 @@ internal static void AddPathsToVertexList(Paths64 paths, PathType polytype, bool prev_v = curr_v; } } - if (prev_v == null || prev_v.prev == null) continue; + if (prev_v?.prev == null) continue; if (!isOpen && prev_v.pt == v0!.pt) prev_v = prev_v.prev; prev_v.next = v0; v0!.prev = prev_v; if (!isOpen && prev_v.next == prev_v) continue; // OK, we have a valid path - bool going_up, going_up0; + bool going_up; if (isOpen) { curr_v = v0.next; @@ -290,7 +290,7 @@ internal static void AddPathsToVertexList(Paths64 paths, PathType polytype, bool going_up = prev_v.pt.Y > v0.pt.Y; } - going_up0 = going_up; + bool going_up0 = going_up; prev_v = v0; curr_v = v0.next; while (curr_v != v0) @@ -488,9 +488,7 @@ private static double GetDx(Point64 pt1, Point64 pt2) double dy = pt2.Y - pt1.Y; if (dy != 0) return (pt2.X - pt1.X) / dy; - if (pt2.X > pt1.X) - return double.NegativeInfinity; - return double.PositiveInfinity; + return pt2.X > pt1.X ? double.NegativeInfinity : double.PositiveInfinity; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -549,17 +547,13 @@ private static void SetDx(Active ae) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vertex NextVertex(Active ae) { - if (ae.windDx > 0) - return ae.vertexTop!.next!; - return ae.vertexTop!.prev!; + return ae.windDx > 0 ? ae.vertexTop!.next! : ae.vertexTop!.prev!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vertex PrevPrevVertex(Active ae) { - if (ae.windDx > 0) - return ae.vertexTop!.prev!.prev!; - return ae.vertexTop!.next!.next!; + return ae.windDx > 0 ? ae.vertexTop!.prev!.prev! : ae.vertexTop!.next!.next!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -577,8 +571,7 @@ private static bool IsMaxima(Active ae) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Active? GetMaximaPair(Active ae) { - Active? ae2; - ae2 = ae.nextInAEL; + Active? ae2 = ae.nextInAEL; while (ae2 != null) { if (ae2.vertexTop == ae.vertexTop) return ae2; // Found! @@ -621,12 +614,9 @@ private struct IntersectListSort : IComparer { public readonly int Compare(IntersectNode a, IntersectNode b) { - if (a.pt.Y == b.pt.Y) - { - if (a.pt.X == b.pt.X) return 0; - return (a.pt.X < b.pt.X) ? -1 : 1; - } - return (a.pt.Y > b.pt.Y) ? -1 : 1; + if (a.pt.Y != b.pt.Y) return (a.pt.Y > b.pt.Y) ? -1 : 1; + if (a.pt.X == b.pt.X) return 0; + return (a.pt.X < b.pt.X) ? -1 : 1; } } @@ -910,7 +900,7 @@ private bool IsContributingClosed(Active ae) { FillRule.Positive => ae.windCount2 > 0, FillRule.Negative => ae.windCount2 < 0, - _ => ae.windCount2 != 0, + _ => ae.windCount2 != 0 }; case ClipType.Union: @@ -918,7 +908,7 @@ private bool IsContributingClosed(Active ae) { FillRule.Positive => ae.windCount2 <= 0, FillRule.Negative => ae.windCount2 >= 0, - _ => ae.windCount2 == 0, + _ => ae.windCount2 == 0 }; case ClipType.Difference: @@ -926,7 +916,7 @@ private bool IsContributingClosed(Active ae) { FillRule.Positive => (ae.windCount2 <= 0), FillRule.Negative => (ae.windCount2 >= 0), - _ => (ae.windCount2 == 0), + _ => (ae.windCount2 == 0) }; return (GetPolyType(ae) == PathType.Subject) ? result : !result; @@ -1122,8 +1112,6 @@ private static bool IsValidAelOrder(Active resident, Active newcomer) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void InsertLeftEdge(Active ae) { - Active ae2; - if (_actives == null) { ae.prevInAEL = null; @@ -1139,7 +1127,7 @@ private void InsertLeftEdge(Active ae) } else { - ae2 = _actives; + Active ae2 = _actives; while (ae2.nextInAEL != null && IsValidAelOrder(ae2.nextInAEL, ae)) ae2 = ae2.nextInAEL; //don't separate joined edges @@ -1162,13 +1150,12 @@ private static void InsertRightEdge(Active ae, Active ae2) private void InsertLocalMinimaIntoAEL(long botY) { - LocalMinima localMinima; - Active? leftBound, rightBound; // Add any local minima (if any) at BotY ... // NB horizontal local minima edges should contain locMin.vertex.prev while (HasLocMinAtY(botY)) { - localMinima = PopLocalMinima(); + LocalMinima localMinima = PopLocalMinima(); + Active? leftBound; if ((localMinima.vertex.flags & VertexFlags.OpenStart) != VertexFlags.None) { leftBound = null; @@ -1188,6 +1175,7 @@ private void InsertLocalMinimaIntoAEL(long botY) SetDx(leftBound); } + Active? rightBound; if ((localMinima.vertex.flags & VertexFlags.OpenEnd) != VertexFlags.None) { rightBound = null; @@ -1461,8 +1449,13 @@ private static OutPt AddOutPt(Active ae, Point64 pt) OutPt opFront = outrec.pts!; OutPt opBack = opFront.next!; - if (toFront && (pt == opFront.pt)) return opFront; - else if (!toFront && (pt == opBack.pt)) return opBack; + switch (toFront) + { + case true when (pt == opFront.pt): + return opFront; + case false when (pt == opBack.pt): + return opBack; + } OutPt newOp = new OutPt(pt, outrec); opBack.prev = newOp; @@ -1698,7 +1691,7 @@ private void IntersectEdges(Active ae1, Active ae2, Point64 pt) else if (IsFront(ae1) || (ae1.outrec == ae2.outrec)) { // this 'else if' condition isn't strictly needed but - // it's sensible to split polygons that ony touch at + // it's sensible to split polygons that only touch at // a common vertex (not at common edges). resultOp = AddLocalMaxPoly(ae1, ae2, pt); #if USINGZ @@ -1829,10 +1822,8 @@ private void AdjustCurrXAndCopyToSEL(long topY) ae.prevInSEL = ae.prevInAEL; ae.nextInSEL = ae.nextInAEL; ae.jump = ae.nextInSEL; - if (ae.joinWith == JoinWith.Left) - ae.curX = ae.prevInAEL!.curX; // this also avoids complications - else - ae.curX = TopX(ae, topY); + ae.curX = ae.joinWith == JoinWith.Left ? ae.prevInAEL!.curX : // this also avoids complications + TopX(ae, topY); // NB don't update ae.curr.Y yet (see AddNewIntersectNode) ae = ae.nextInAEL; } @@ -1840,7 +1831,7 @@ private void AdjustCurrXAndCopyToSEL(long topY) protected void ExecuteInternal(ClipType ct, FillRule fillRule) { - if (ct == ClipType.None) return; + if (ct == ClipType.NoClip) return; _fillrule = fillRule; _cliptype = ct; Reset(); @@ -1868,11 +1859,9 @@ protected void ExecuteInternal(ClipType ct, FillRule fillRule) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DoIntersections(long topY) { - if (BuildIntersectList(topY)) - { - ProcessIntersectList(); - DisposeIntersectNodes(); - } + if (!BuildIntersectList(topY)) return; + ProcessIntersectList(); + DisposeIntersectNodes(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1892,23 +1881,33 @@ private void AddNewIntersectNode(Active ae1, Active ae2, long topY) { double absDx1 = Math.Abs(ae1.dx); double absDx2 = Math.Abs(ae2.dx); - if (absDx1 > 100 && absDx2 > 100) + switch (absDx1 > 100) { - if (absDx1 > absDx2) + case true when absDx2 > 100: + { + if (absDx1 > absDx2) + ip = InternalClipper.GetClosestPtOnSegment(ip, ae1.bot, ae1.top); + else + ip = InternalClipper.GetClosestPtOnSegment(ip, ae2.bot, ae2.top); + break; + } + case true: ip = InternalClipper.GetClosestPtOnSegment(ip, ae1.bot, ae1.top); - else - ip = InternalClipper.GetClosestPtOnSegment(ip, ae2.bot, ae2.top); - } - else if (absDx1 > 100) - ip = InternalClipper.GetClosestPtOnSegment(ip, ae1.bot, ae1.top); - else if (absDx2 > 100) - ip = InternalClipper.GetClosestPtOnSegment(ip, ae2.bot, ae2.top); - else - { - if (ip.Y < topY) ip.Y = topY; - else ip.Y = _currentBotY; - if (absDx1 < absDx2) ip.X = TopX(ae1, ip.Y); - else ip.X = TopX(ae2, ip.Y); + break; + default: + { + if (absDx2 > 100) + ip = InternalClipper.GetClosestPtOnSegment(ip, ae2.bot, ae2.top); + else + { + if (ip.Y < topY) ip.Y = topY; + else ip.Y = _currentBotY; + if (absDx1 < absDx2) ip.X = TopX(ae1, ip.Y); + else ip.X = TopX(ae2, ip.Y); + } + + break; + } } } IntersectNode node = new IntersectNode(ip, ae1, ae2); @@ -1937,7 +1936,7 @@ private static void Insert1Before2InSEL(Active ae1, Active ae2) private bool BuildIntersectList(long topY) { - if (_actives == null || _actives.nextInAEL == null) return false; + if (_actives?.nextInAEL == null) 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. @@ -1948,23 +1947,23 @@ private bool BuildIntersectList(long topY) // stored in FIntersectList ready to be processed in ProcessIntersectList. // Re merge sorts see https://stackoverflow.com/a/46319131/359538 - Active? left = _sel, right, lEnd, rEnd, currBase, prevBase, tmp; + Active? left = _sel; while (left!.jump != null) { - prevBase = null; - while (left != null && left.jump != null) + Active? prevBase = null; + while (left?.jump != null) { - currBase = left; - right = left.jump; - lEnd = right; - rEnd = right.jump; + Active? currBase = left; + Active? right = left.jump; + Active? lEnd = right; + Active? rEnd = right.jump; left.jump = rEnd; while (left != lEnd && right != rEnd) { if (right!.curX < left!.curX) { - tmp = right.prevInSEL!; + Active? tmp = right.prevInSEL!; for (; ; ) { AddNewIntersectNode(tmp, right, topY); @@ -1976,13 +1975,11 @@ private bool BuildIntersectList(long topY) right = ExtractFromSEL(tmp); lEnd = right; Insert1Before2InSEL(tmp, left); - if (left == currBase) - { - currBase = tmp; - currBase.jump = rEnd; - if (prevBase == null) _sel = currBase; - else prevBase.jump = currBase; - } + if (left != currBase) continue; + currBase = tmp; + currBase.jump = rEnd; + if (prevBase == null) _sel = currBase; + else prevBase.jump = currBase; } else left = left.nextInSEL; } @@ -2103,7 +2100,7 @@ private void AddToHorzSegList(OutPt op) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private OutPt GetLastOp(Active hotEdge) + private static OutPt GetLastOp(Active hotEdge) { OutRec outrec = hotEdge.outrec!; return (hotEdge == outrec.frontEdge) ? @@ -2126,7 +2123,6 @@ private void DoHorizontal(Active horz) * / | / | / * *******************************************************************************/ { - Point64 pt; bool horzIsOpen = IsOpen(horz); long Y = horz.bot.Y; @@ -2178,6 +2174,7 @@ private void DoHorizontal(Active horz) // if horzEdge is a maxima, keep going until we reach // its maxima pair, otherwise check for break conditions + Point64 pt; if (vertex_max != horz.vertexTop || IsOpenEnd(horz)) { // otherwise stop when 'ae' is beyond the end of the horizontal line @@ -2243,7 +2240,7 @@ private void DoHorizontal(Active horz) DeleteFromAEL(horz); return; } - else if (NextVertex(horz).pt.Y != horz.top.Y) + if (NextVertex(horz).pt.Y != horz.top.Y) break; //still more horizontals in bound to process ... @@ -2300,30 +2297,26 @@ private void DoTopOfScanbeam(long y) [MethodImpl(MethodImplOptions.AggressiveInlining)] private Active? DoMaxima(Active ae) { - Active? prevE; - Active? nextE, maxPair; - prevE = ae.prevInAEL; - nextE = ae.nextInAEL; + Active? prevE = ae.prevInAEL; + Active? nextE = ae.nextInAEL; if (IsOpenEnd(ae)) { if (IsHotEdge(ae)) AddOutPt(ae, ae.top); - if (!IsHorizontal(ae)) + if (IsHorizontal(ae)) return nextE; + if (IsHotEdge(ae)) { - if (IsHotEdge(ae)) - { - if (IsFront(ae)) - ae.outrec!.frontEdge = null; - else - ae.outrec!.backEdge = null; - ae.outrec = null; - } - DeleteFromAEL(ae); + if (IsFront(ae)) + ae.outrec!.frontEdge = null; + else + ae.outrec!.backEdge = null; + ae.outrec = null; } + DeleteFromAEL(ae); return nextE; } - maxPair = GetMaximaPair(ae); + Active? maxPair = GetMaximaPair(ae); if (maxPair == null) return nextE; // eMaxPair is horizontal if (IsJoined(ae)) Split(ae, ae.top); @@ -2521,17 +2514,16 @@ private static OutPt DuplicateOp(OutPt op, bool insert_after) return result; } - private int HorzSegSort(HorzSegment? hs1, HorzSegment? hs2) + private static int HorzSegSort(HorzSegment? hs1, HorzSegment? hs2) { if (hs1 == null || hs2 == null) return 0; if (hs1.rightOp == null) { return hs2.rightOp == null ? 0 : 1; } - else if (hs2.rightOp == null) + if (hs2.rightOp == null) return -1; - else - return hs1.leftOp!.pt.X.CompareTo(hs2.leftOp!.pt.X); + return hs1.leftOp!.pt.X.CompareTo(hs2.leftOp!.pt.X); } private void ConvertHorzSegsToJoins() @@ -2636,7 +2628,7 @@ private static PointInPolygonResult PointInOpPolygon(Point64 pt, OutPt op) while (op2 != op && op2.pt.Y > pt.Y) op2 = op2.next!; if (op2 == op) break; - // must have touched or crossed the pt.Y horizonal + // 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 @@ -2664,29 +2656,34 @@ private static PointInPolygonResult PointInOpPolygon(Point64 pt, OutPt op) op2 = op2.next!; } - if (isAbove != startingAbove) + if (isAbove == startingAbove) return val == 0 ? PointInPolygonResult.IsOutside : PointInPolygonResult.IsInside; { double d = InternalClipper.CrossProduct(op2.prev.pt, op2.pt, pt); if (d == 0) return PointInPolygonResult.IsOn; if ((d < 0) == isAbove) val = 1 - val; } - if (val == 0) return PointInPolygonResult.IsOutside; - else return PointInPolygonResult.IsInside; + return val == 0 ? PointInPolygonResult.IsOutside : PointInPolygonResult.IsInside; } private static 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; + PointInPolygonResult result = PointInOpPolygon(op.pt, op2); + switch (result) + { + case PointInPolygonResult.IsOutside: + ++outside_cnt; + break; + case PointInPolygonResult.IsInside: + --outside_cnt; + break; + } op = op.next!; } while (op != op1 && Math.Abs(outside_cnt) < 2); if (Math.Abs(outside_cnt) > 1) return (outside_cnt < 0); @@ -2696,7 +2693,7 @@ private static bool Path1InsidePath2(OutPt op1, OutPt op2) return InternalClipper.PointInPolygon(mp, path2) != PointInPolygonResult.IsOutside; } - private void MoveSplits(OutRec fromOr, OutRec toOr) + private static void MoveSplits(OutRec fromOr, OutRec toOr) { if (fromOr.splits == null) return; toOr.splits ??= new List(); @@ -2889,33 +2886,29 @@ private void DoSplitOp(OutRec outrec, OutPt splitOp) // 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 newOutRec = NewOutRec(); - newOutRec.owner = outrec.owner; - splitOp.outrec = newOutRec; - splitOp.next.outrec = newOutRec; + if (!(absArea2 > 1) || + (!(absArea2 > absArea1) && + ((area2 > 0) != (area1 > 0)))) return; + OutRec newOutRec = NewOutRec(); + newOutRec.owner = outrec.owner; + splitOp.outrec = newOutRec; + splitOp.next.outrec = newOutRec; - OutPt newOp = new OutPt(ip, newOutRec) { prev = splitOp.next, next = splitOp }; - newOutRec.pts = newOp; - splitOp.prev = newOp; - splitOp.next.next = newOp; + OutPt newOp = new OutPt(ip, newOutRec) { prev = splitOp.next, next = splitOp }; + newOutRec.pts = newOp; + splitOp.prev = newOp; + splitOp.next.next = newOp; - if (_using_polytree) - { - if (Path1InsidePath2(prevOp, newOp)) - { - newOutRec.splits ??= new List(); - newOutRec.splits.Add(outrec.idx); - } - else - { - outrec.splits ??= new List(); - outrec.splits.Add(newOutRec.idx); - } - } + if (!_using_polytree) return; + if (Path1InsidePath2(prevOp, newOp)) + { + newOutRec.splits ??= new List(); + newOutRec.splits.Add(outrec.idx); + } + else + { + outrec.splits ??= new List(); + outrec.splits.Add(newOutRec.idx); } //else { splitOp = null; splitOp.next = null; } } @@ -2936,8 +2929,8 @@ private void FixSelfIntersects(OutRec outrec) op2 = outrec.pts; continue; } - else - op2 = op2.next; + + op2 = op2.next; if (op2 == outrec.pts) break; } } @@ -2975,8 +2968,7 @@ internal static bool BuildPath(OutPt? op, bool reverse, bool isOpen, Path64 path op2 = op2.next!; } - if (path.Count == 3 && !isOpen && IsVerySmallTriangle(op2)) return false; - else return true; + return path.Count != 3 || isOpen || !IsVerySmallTriangle(op2); } protected bool BuildPaths(Paths64 solutionClosed, Paths64 solutionOpen) @@ -3048,14 +3040,12 @@ private bool CheckSplitOwner(OutRec outrec, List? splits) if (split == null || split == outrec || split.recursiveSplit == outrec) continue; split.recursiveSplit = outrec; //#599 if (split.splits != null && CheckSplitOwner(outrec, split.splits)) return true; - if (IsValidOwner(outrec, split) && - CheckBounds(split) && - split.bounds.Contains(outrec.bounds) && - Path1InsidePath2(outrec.pts!, split.pts!)) - { - outrec.owner = split; //found in split - return true; - } + if (!IsValidOwner(outrec, split) || + !CheckBounds(split) || + !split.bounds.Contains(outrec.bounds) || + !Path1InsidePath2(outrec.pts!, split.pts!)) continue; + outrec.owner = split; //found in split + return true; } return false; } @@ -3070,7 +3060,7 @@ private void RecursiveCheckOwners(OutRec outrec, PolyPathBase polypath) { if (outrec.owner.splits != null && CheckSplitOwner(outrec, outrec.owner.splits)) break; - else if (outrec.owner.pts != null && CheckBounds(outrec.owner) && + if (outrec.owner.pts != null && CheckBounds(outrec.owner) && Path1InsidePath2(outrec.pts!, outrec.owner.pts!)) break; outrec.owner = outrec.owner.owner; } @@ -3235,7 +3225,7 @@ public ZCallback64? ZCallback { public class ClipperD : ClipperBase { - private readonly string precision_range_error = "Error: Precision is out of range."; + private const string precision_range_error = "Error: Precision is out of range."; private readonly double _scale; private readonly double _invScale; @@ -3393,12 +3383,10 @@ public bool Execute(ClipType clipType, FillRule fillRule, PolyTreeD polytree, Pa } ClearSolutionOnly(); if (!success) return false; - if (oPaths.Count > 0) - { - openPaths.EnsureCapacity(oPaths.Count); - foreach (Path64 path in oPaths) - openPaths.Add(Clipper.ScalePathD(path, _invScale)); - } + if (oPaths.Count <= 0) return true; + openPaths.EnsureCapacity(oPaths.Count); + foreach (Path64 path in oPaths) + openPaths.Add(Clipper.ScalePathD(path, _invScale)); return true; } @@ -3453,7 +3441,7 @@ public object Current } } - }; + } public bool IsHole => GetIsHole(); @@ -3492,9 +3480,9 @@ internal string ToStringInternal(int idx, int level) if (_childs.Count == 1) plural = ""; padding = padding.PadLeft(level * 2); if ((level & 1) == 0) - result += string.Format("{0}+- hole ({1}) contains {2} nested polygon{3}.\n", padding, idx, _childs.Count, plural); + result += $"{padding}+- hole ({idx}) contains {_childs.Count} nested polygon{plural}.\n"; else - result += string.Format("{0}+- polygon ({1}) contains {2} hole{3}.\n", padding, idx, _childs.Count, plural); + result += $"{padding}+- polygon ({idx}) contains {_childs.Count} hole{plural}.\n"; for (int i = 0; i < Count; i++) if (_childs[i].Count > 0) @@ -3507,7 +3495,7 @@ public override string ToString() if (Level > 0) return ""; //only accept tree root string plural = "s"; if (_childs.Count == 1) plural = ""; - string result = string.Format("Polytree with {0} polygon{1}.\n", _childs.Count, plural); + string result = $"Polytree with {_childs.Count} polygon{plural}.\n"; for (int i = 0; i < Count; i++) if (_childs[i].Count > 0) result += _childs[i].ToStringInternal(i, 1); diff --git a/CSharp/Clipper2Lib/Clipper.Minkowski.cs b/CSharp/Clipper2Lib/Clipper.Minkowski.cs index 707fc111..815d9940 100644 --- a/CSharp/Clipper2Lib/Clipper.Minkowski.cs +++ b/CSharp/Clipper2Lib/Clipper.Minkowski.cs @@ -1,10 +1,10 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 15 October 2022 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Date : 10 October 2024 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2024 * * Purpose : Minkowski Sum and Difference * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #nullable enable @@ -12,7 +12,7 @@ namespace Drecom.Clipper2Lib { - public class Minkowski + public static class Minkowski { private static Paths64 MinkowskiInternal(Path64 pattern, Path64 path, bool isSum, bool isClosed) { diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index d27903c6..74e97e18 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,10 +1,10 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 17 April 2024 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2024 * +* Date : 22 January 2025 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2025 * * Purpose : Path Offset (Inflate/Shrink) * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ using System; @@ -19,7 +19,7 @@ public enum JoinType Square, Bevel, Round - }; + } public enum EndType { @@ -28,7 +28,7 @@ public enum EndType Butt, Square, Round - }; + } public class ClipperOffset { @@ -67,7 +67,21 @@ public Group(Paths64 paths, JoinType joinType, EndType endType = EndType.Polygon } } - private static readonly double Tolerance = 1.0E-12; + private const double Tolerance = 1.0E-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 private readonly List _groupList = new List(); private Path64 pathOut = new Path64(); @@ -91,7 +105,7 @@ public Group(Paths64 paths, JoinType joinType, EndType endType = EndType.Polygon public delegate double DeltaCallback64(Path64 path, PathD path_norms, int currPt, int prevPt); - public ClipperOffset.DeltaCallback64? DeltaCallback { get; set; } + public DeltaCallback64? DeltaCallback { get; set; } #if USINGZ internal void ZCB(Point64 bot1, Point64 top1, @@ -185,10 +199,8 @@ private void ExecuteInternal(double delta) FillRule fillRule = pathsReversed ? FillRule.Negative : FillRule.Positive; // clean up self-intersections ... - Clipper64 c = new Clipper64(); - c.PreserveCollinear = PreserveCollinear; - // the solution should retain the orientation of the input - c.ReverseSolution = ReverseSolution != pathsReversed; + Clipper64 c = new Clipper64 { PreserveCollinear = PreserveCollinear, // the solution should retain the orientation of the input + ReverseSolution = ReverseSolution != pathsReversed }; #if USINGZ c.ZCallback = ZCB; #endif @@ -476,9 +488,7 @@ private void DoRound(Path64 path, int j, int k, double angle) // when DeltaCallback is assigned, _groupDelta won't be constant, // so we'll need to do the following calculations for *every* vertex. double absDelta = Math.Abs(_groupDelta); - double arcTol = ArcTolerance > 0.01 ? - ArcTolerance : - Math.Log10(2 + absDelta) * InternalClipper.defaultArcTolerance; + double arcTol = ArcTolerance > 0.01 ? ArcTolerance : absDelta * arc_const; double stepsPer360 = Math.PI / Math.Acos(1 - arcTol / absDelta); _stepSin = Math.Sin((2 * Math.PI) / stepsPer360); _stepCos = Math.Cos((2 * Math.PI) / stepsPer360); @@ -513,8 +523,8 @@ private void BuildNormals(Path64 path) { int cnt = path.Count; _normals.Clear(); + if (cnt == 0) return; _normals.EnsureCapacity(cnt); - for (int i = 0; i < cnt - 1; i++) _normals.Add(GetUnitNormal(path[i], path[i + 1])); _normals.Add(GetUnitNormal(path[cnt - 1], path[0])); @@ -548,11 +558,12 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) if (cosA > -0.999 && (sinA * _groupDelta < 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. pathOut.Add(GetPerpendic(path[j], _normals[k])); - // this extra point is the only simple way to ensure that path reversals - // (ie over-shrunk paths) are fully cleaned out with the trailing union op. - // However it's probably safe to skip this whenever an angle is almost flat. - if (cosA < 0.99) pathOut.Add(path[j]); // (#405) + pathOut.Add(path[j]); // (#405, #873, #916) pathOut.Add(GetPerpendic(path[j], _normals[j])); } else if ((cosA > 0.999) && (_joinType != JoinType.Round)) @@ -560,18 +571,25 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) // almost straight - less than 2.5 degree (#424, #482, #526 & #724) DoMiter(path, j, k, cosA); } - else if (_joinType == JoinType.Miter) + else switch (_joinType) { // miter unless the angle is sufficiently acute to exceed ML - if (cosA > _mitLimSqr - 1) DoMiter(path, j, k, cosA); - else DoSquare(path, j, k); + case JoinType.Miter when cosA > _mitLimSqr - 1: + DoMiter(path, j, k, cosA); + break; + case JoinType.Miter: + DoSquare(path, j, k); + break; + case JoinType.Round: + DoRound(path, j, k, Math.Atan2(sinA, cosA)); + break; + case JoinType.Bevel: + DoBevel(path, j, k); + break; + default: + DoSquare(path, j, k); + break; } - else if (_joinType == JoinType.Round) - DoRound(path, j, k, Math.Atan2(sinA, cosA)); - else if (_joinType == JoinType.Bevel) - DoBevel(path, j, k); - else - DoSquare(path, j, k); k = j; } @@ -674,14 +692,7 @@ private void DoGroupOffset(Group group) if (group.joinType == JoinType.Round || group.endType == EndType.Round) { - // calculate the number of steps required to approximate a circle - // (see http://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 = ArcTolerance > 0.01 ? - ArcTolerance : - Math.Log10(2 + absDelta) * InternalClipper.defaultArcTolerance; + double arcTol = ArcTolerance > 0.01 ? ArcTolerance : absDelta * arc_const; double stepsPer360 = Math.PI / Math.Acos(1 - arcTol / absDelta); _stepSin = Math.Sin((2 * Math.PI) / stepsPer360); _stepCos = Math.Cos((2 * Math.PI) / stepsPer360); @@ -697,50 +708,61 @@ private void DoGroupOffset(Group group) pathOut = new Path64(); int cnt = p.Count; - if (cnt == 1) + switch (cnt) { - Point64 pt = p[0]; - - if (DeltaCallback != null) + case 1: { - _groupDelta = DeltaCallback(p, _normals, 0, 0); - if (group.pathsReversed) _groupDelta = -_groupDelta; - absDelta = Math.Abs(_groupDelta); - } - - // single vertex so build a circle or square ... - if (group.endType == EndType.Round) - { - double r = absDelta; - int steps = (int) Math.Ceiling(_stepsPerRad * 2 * Math.PI); - pathOut = Clipper.Ellipse(pt, r, r, steps); + Point64 pt = p[0]; + + if (DeltaCallback != null) + { + _groupDelta = DeltaCallback(p, _normals, 0, 0); + if (group.pathsReversed) _groupDelta = -_groupDelta; + absDelta = Math.Abs(_groupDelta); + } + + // single vertex so build a circle or square ... + if (group.endType == EndType.Round) + { + int steps = (int) Math.Ceiling(_stepsPerRad * 2 * Math.PI); + pathOut = Clipper.Ellipse(pt, absDelta, absDelta, steps); #if USINGZ pathOut = InternalClipper.SetZ(pathOut, pt.Z); #endif - } - else - { - int d = (int) Math.Ceiling(_groupDelta); - Rect64 r = new Rect64(pt.X - d, pt.Y - d, pt.X + d, pt.Y + d); - pathOut = r.AsPath(); + } + else + { + int d = (int) Math.Ceiling(_groupDelta); + Rect64 r = new Rect64(pt.X - d, pt.Y - d, pt.X + d, pt.Y + d); + pathOut = r.AsPath(); #if USINGZ pathOut = InternalClipper.SetZ(pathOut, pt.Z); #endif + } + _solution.Add(pathOut); + continue; // end of offsetting a single point } - _solution.Add(pathOut); - continue; - } // end of offsetting a single point - + case 2 when group.endType == EndType.Joined: + _endType = (group.joinType == JoinType.Round) ? + EndType.Round : + EndType.Square; + break; + } - if (cnt == 2 && group.endType == EndType.Joined) - _endType = (group.joinType == JoinType.Round) ? - EndType.Round : - EndType.Square; BuildNormals(p); - if (_endType == EndType.Polygon) OffsetPolygon(group, p); - else if (_endType == EndType.Joined) OffsetOpenJoined(group, p); - else OffsetOpenPath(group, p); + switch (_endType) + { + case EndType.Polygon: + OffsetPolygon(group, p); + break; + case EndType.Joined: + OffsetOpenJoined(group, p); + break; + default: + OffsetOpenPath(group, p); + break; + } } } } diff --git a/CSharp/Clipper2Lib/Clipper.RectClip.cs b/CSharp/Clipper2Lib/Clipper.RectClip.cs index 68d0c14d..66205335 100644 --- a/CSharp/Clipper2Lib/Clipper.RectClip.cs +++ b/CSharp/Clipper2Lib/Clipper.RectClip.cs @@ -1,10 +1,10 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 7 May 2024 * -* Website : http://www.angusj.com * +* Date : 10 October 2024 * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2024 * * Purpose : FAST rectangular clipping * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #nullable enable @@ -33,7 +33,7 @@ public class RectClip64 protected enum Location { left, top, right, bottom, inside - }; + } readonly protected Rect64 rect_; readonly protected Point64 mp_; @@ -113,8 +113,7 @@ private static bool IsClockwise(Location prev, Location curr, { if (AreOpposites(prev, curr)) return InternalClipper.CrossProduct(prevPt, rectMidPoint, currPt) < 0; - else - return HeadingClockwise(prev, curr); + return HeadingClockwise(prev, curr); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -173,7 +172,7 @@ private static bool IsHeadingClockwise(Point64 pt1, Point64 pt2, int edgeIdx) 0 => pt2.Y < pt1.Y, 1 => pt2.X > pt1.X, 2 => pt2.Y > pt1.Y, - _ => pt2.X < pt1.X, + _ => pt2.X < pt1.X }; } @@ -206,11 +205,9 @@ private static void UncoupleEdge(OutPt2 op) for (int i = 0; i < op.edge.Count; i++) { OutPt2? op2 = op.edge[i]; - if (op2 == op) - { - op.edge[i] = null; - break; - } + if (op2 != op) continue; + op.edge[i] = null; + break; } op.edge = null; } @@ -229,10 +226,7 @@ private static void SetNewOwner(OutPt2 op, int newIdx) private void AddCorner(Location prev, Location curr) { - if (HeadingClockwise(prev, curr)) - Add(rectPath_[(int) prev]); - else - Add(rectPath_[(int) curr]); + Add(HeadingClockwise(prev, curr) ? rectPath_[(int) prev] : rectPath_[(int) curr]); } private void AddCorner(ref Location loc, bool isClockwise) @@ -290,17 +284,17 @@ private static bool GetSegmentIntersection(Point64 p1, { ip = p1; if (res2 == 0) return false; // segments are collinear - else if (p1 == p3 || p1 == p4) return true; + 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)); + if (IsHorizontal(p3, p4)) return ((p1.X > p3.X) == (p1.X < p4.X)); + return ((p1.Y > p3.Y) == (p1.Y < p4.Y)); } - else if (res2 == 0) + 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 (IsHorizontal(p3, p4)) return ((p2.X > p3.X) == (p2.X < p4.X)); + return ((p2.Y > p3.Y) == (p2.Y < p4.Y)); } if ((res1 > 0) == (res2 > 0)) @@ -315,15 +309,15 @@ private static bool GetSegmentIntersection(Point64 p1, { 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)); + if (IsHorizontal(p1, p2)) return ((p3.X > p1.X) == (p3.X < p2.X)); + return ((p3.Y > p1.Y) == (p3.Y < p2.Y)); } - else if (res4 == 0) + 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 (IsHorizontal(p1, p2)) return ((p4.X > p1.X) == (p4.X < p2.X)); + return ((p4.Y > p1.Y) == (p4.Y < p2.Y)); } if ((res3 > 0) == (res4 > 0)) { @@ -346,62 +340,54 @@ static protected bool GetIntersection(Path64 rectPath, Point64 p, Point64 p2, re case Location.left: if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip)) return true; - else if (p.Y < rectPath[0].Y && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip)) + if (p.Y < rectPath[0].Y && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip)) { loc = Location.top; return true; } - else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) - { - loc = Location.bottom; - return true; - } - else return false; + + if (!GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) return false; + loc = Location.bottom; + return true; case Location.right: if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) return true; - else if (p.Y < rectPath[0].Y && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip)) + if (p.Y < rectPath[0].Y && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip)) { loc = Location.top; return true; } - else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) - { - loc = Location.bottom; - return true; - } - else return false; + + if (!GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) return false; + loc = Location.bottom; + return true; case Location.top: if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip)) return true; - else if (p.X < rectPath[0].X && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip)) + if (p.X < rectPath[0].X && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip)) { loc = Location.left; return true; } - else if (p.X > rectPath[1].X && GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) - { - loc = Location.right; - return true; - } - else return false; + + if (p.X <= rectPath[1].X || !GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) return false; + loc = Location.right; + return true; case Location.bottom: if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) return true; - else if (p.X < rectPath[3].X && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip)) + if (p.X < rectPath[3].X && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip)) { loc = Location.left; return true; } - else if (p.X > rectPath[2].X && GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) - { - loc = Location.right; - return true; - } - else return false; + + if (p.X <= rectPath[2].X || !GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) return false; + loc = Location.right; + return true; default: if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip)) @@ -409,22 +395,20 @@ static protected bool GetIntersection(Path64 rectPath, Point64 p, Point64 p2, re loc = Location.left; return true; } - else if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip)) + if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip)) { loc = Location.top; return true; } - else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) + if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) { loc = Location.right; return true; } - else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) - { - loc = Location.bottom; - return true; - } - else return false; + + if (!GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) return false; + loc = Location.bottom; + return true; } } @@ -498,6 +482,23 @@ protected void GetNextLocation(Path64 path, } // switch } + private static bool StartLocsAreClockwise(List startLocs) + { + int result = 0; + for (int i = 1; i < startLocs.Count; i++) + { + int d = (int)startLocs[i] - (int)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; + } + private void ExecuteInternal(Path64 path) { if (path.Count < 3 || rect_.IsEmpty()) return; @@ -619,17 +620,15 @@ private void ExecuteInternal(Path64 path) if (firstCross == Location.inside) { // path never intersects - if (startingLoc != Location.inside) + if (startingLoc == Location.inside) return; + if (!pathBounds_.Contains(rect_) || + !Path1ContainsPath2(path, rectPath_)) return; + bool startLocsClockwise = StartLocsAreClockwise(startLocs); + for (int j = 0; j < 4; j++) { - if (pathBounds_.Contains(rect_) && - Path1ContainsPath2(path, rectPath_)) - { - for (int j = 0; j < 4; j++) - { - Add(rectPath_[j]); - AddToEdge(edges_[j * 2], results_[0]!); - } - } + int k = startLocsClockwise ? j : 3 - j; // ie reverse result path + Add(rectPath_[k]); + AddToEdge(edges_[k * 2], results_[0]!); } } else if (loc != Location.inside && @@ -661,7 +660,7 @@ public Paths64 Execute(Paths64 paths) pathBounds_ = Clipper.GetBounds(path); if (!rect_.Intersects(pathBounds_)) continue; // the path must be completely outside fRect - else if (rect_.Contains(pathBounds_)) + if (rect_.Contains(pathBounds_)) { // the path must be completely inside rect_ result.Add(path); @@ -730,13 +729,11 @@ private void CheckEdges() uint combinedSet = (edgeSet1 & edgeSet2); for (int j = 0; j < 4; ++j) { - if ((combinedSet & (1 << j)) != 0) - { - if (IsHeadingClockwise(op2.prev!.pt, op2.pt, j)) - AddToEdge(edges_[j * 2], op2); - else - AddToEdge(edges_[j * 2 + 1], op2); - } + if ((combinedSet & (1 << j)) == 0) continue; + if (IsHeadingClockwise(op2.prev!.pt, op2.pt, j)) + AddToEdge(edges_[j * 2], op2); + else + AddToEdge(edges_[j * 2 + 1], op2); } } edgeSet1 = edgeSet2; @@ -751,11 +748,10 @@ private void TidyEdgePair(int idx, List cw, List ccw) bool isHorz = ((idx == 1) || (idx == 3)); bool cwIsTowardLarger = ((idx == 1) || (idx == 2)); int i = 0, j = 0; - OutPt2? p1, p2, p1a, p2a, op, op2; while (i < cw.Count) { - p1 = cw[i]; + OutPt2? p1 = cw[i]; if (p1 == null || p1.next == p1.prev) { cw[i++] = null; @@ -774,6 +770,9 @@ private void TidyEdgePair(int idx, List cw, List ccw) continue; } + OutPt2? p2; + OutPt2? p1a; + OutPt2? p2a; if (cwIsTowardLarger) { // p1 >>>> p1a; @@ -836,6 +835,8 @@ private void TidyEdgePair(int idx, List cw, List ccw) SetNewOwner(p1a, new_idx); } + OutPt2? op; + OutPt2? op2; if (cwIsTowardLarger) { op = p2; @@ -923,7 +924,7 @@ private void TidyEdgePair(int idx, List cw, List ccw) } } - private Path64 GetPath(OutPt2? op) + private static Path64 GetPath(OutPt2? op) { Path64 result = new Path64(); if (op == null || op.prev == op.next) return result; @@ -986,7 +987,7 @@ internal RectClipLines64(Rect64 rect) : base(rect) { } return result; } - private Path64 GetPath(OutPt2? op) + private static Path64 GetPath(OutPt2? op) { Path64 result = new Path64(); if (op == null || op == op.next) return result; diff --git a/CSharp/Clipper2Lib/Clipper.cs b/CSharp/Clipper2Lib/Clipper.cs index 73c2ee46..a50d25d8 100644 --- a/CSharp/Clipper2Lib/Clipper.cs +++ b/CSharp/Clipper2Lib/Clipper.cs @@ -1,14 +1,14 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 10 May 2024 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2024 * +* Date : 22 January 2025 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2025 * * Purpose : This module contains simple functions that will likely cover * * most polygon boolean and offsetting needs, while also avoiding * * the inherent complexities of the other modules. * * Thanks : Special thanks to Thong Nguyen, Guus Kuiper, Phil Stopford, * * : and Daniel Gosnell for their invaluable assistance with C#. * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ #nullable enable @@ -138,9 +138,9 @@ public static void BooleanOp(ClipType clipType, } public static Paths64 InflatePaths(Paths64 paths, double delta, JoinType joinType, - EndType endType, double miterLimit = 2.0) + EndType endType, double miterLimit = 2.0, double arcTolerance = 0.0) { - ClipperOffset co = new ClipperOffset(miterLimit); + ClipperOffset co = new ClipperOffset(miterLimit, arcTolerance); co.AddPaths(paths, joinType, endType); Paths64 solution = new Paths64(); co.Execute(delta, solution); @@ -148,12 +148,12 @@ public static Paths64 InflatePaths(Paths64 paths, double delta, JoinType joinTyp } public static PathsD InflatePaths(PathsD paths, double delta, JoinType joinType, - EndType endType, double miterLimit = 2.0, int precision = 2) + EndType endType, double miterLimit = 2.0, int precision = 2, double arcTolerance = 0.0) { InternalClipper.CheckPrecision(precision); double scale = Math.Pow(10, precision); Paths64 tmp = ScalePaths64(paths, scale); - ClipperOffset co = new ClipperOffset(miterLimit); + ClipperOffset co = new ClipperOffset(miterLimit, arcTolerance); co.AddPaths(tmp, joinType, endType); co.Execute(delta * scale, tmp); // reuse 'tmp' to receive (scaled) solution return ScalePathsD(tmp, 1 / scale); @@ -813,21 +813,31 @@ public static double PerpendicDistFromLineSqrd(Point64 pt, Point64 line1, Point6 internal static void RDP(Path64 path, int begin, int end, double epsSqrd, List flags) { - int idx = 0; - double max_d = 0; - while (end > begin && path[begin] == path[end]) flags[end--] = false; - for (int i = begin + 1; i < end; ++i) + while (true) { - // PerpendicDistFromLineSqrd - avoids expensive Sqrt() - double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]); - if (d <= max_d) continue; - max_d = d; - idx = i; + int idx = 0; + double max_d = 0; + while (end > begin && path[begin] == path[end]) flags[end--] = false; + for (int 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) + { + begin = idx; + continue; + } + + break; } - 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); } public static Path64 RamerDouglasPeucker(Path64 path, double epsilon) @@ -852,21 +862,31 @@ public static Paths64 RamerDouglasPeucker(Paths64 paths, double epsilon) internal static void RDP(PathD path, int begin, int end, double epsSqrd, List flags) { - int idx = 0; - double max_d = 0; - while (end > begin && path[begin] == path[end]) flags[end--] = false; - for (int i = begin + 1; i < end; ++i) + while (true) { - // PerpendicDistFromLineSqrd - avoids expensive Sqrt() - double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]); - if (d <= max_d) continue; - max_d = d; - idx = i; + int idx = 0; + double max_d = 0; + while (end > begin && path[begin] == path[end]) flags[end--] = false; + for (int 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) + { + begin = idx; + continue; + } + + break; } - 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); } public static PathD RamerDouglasPeucker(PathD path, double epsilon) @@ -921,7 +941,7 @@ public static Path64 SimplifyPath(Path64 path, bool[] flags = new bool[len]; double[] dsq = new double[len]; - int curr = 0, prev, start, next, prior2; + int curr = 0; if (isClosedPath) { @@ -941,7 +961,7 @@ public static Path64 SimplifyPath(Path64 path, { if (dsq[curr] > epsSqr) { - start = curr; + int start = curr; do { curr = GetNext(curr, high, ref flags); @@ -949,10 +969,11 @@ public static Path64 SimplifyPath(Path64 path, if (curr == start) break; } - prev = GetPrior(curr, high, ref flags); - next = GetNext(curr, high, ref flags); + int prev = GetPrior(curr, high, ref flags); + int next = GetNext(curr, high, ref flags); if (next == prev) break; + int prior2; if (dsq[next] < dsq[curr]) { prior2 = prev; @@ -995,7 +1016,7 @@ public static PathD SimplifyPath(PathD path, bool[] flags = new bool[len]; double[] dsq = new double[len]; - int curr = 0, prev, start, next, prior2; + int curr = 0; if (isClosedPath) { dsq[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]); @@ -1013,7 +1034,7 @@ public static PathD SimplifyPath(PathD path, { if (dsq[curr] > epsSqr) { - start = curr; + int start = curr; do { curr = GetNext(curr, high, ref flags); @@ -1021,10 +1042,11 @@ public static PathD SimplifyPath(PathD path, if (curr == start) break; } - prev = GetPrior(curr, high, ref flags); - next = GetNext(curr, high, ref flags); + int prev = GetPrior(curr, high, ref flags); + int next = GetNext(curr, high, ref flags); if (next == prev) break; + int prior2; if (dsq[next] < dsq[curr]) { prior2 = prev; @@ -1181,7 +1203,7 @@ private static void ShowPolyPathStructure(PolyPath64 pp, int level) } else { - Console.WriteLine(spaces + caption + string.Format("({0})", pp.Count)); + Console.WriteLine(spaces + caption + $"({pp.Count})"); foreach (PolyPath64 child in pp) { ShowPolyPathStructure(child, level + 1); } } } @@ -1202,7 +1224,7 @@ private static void ShowPolyPathStructure(PolyPathD pp, int level) } else { - Console.WriteLine(spaces + caption + string.Format("({0})", pp.Count)); + Console.WriteLine(spaces + caption + $"({pp.Count})"); foreach (PolyPathD child in pp) { ShowPolyPathStructure(child, level + 1); } } } diff --git a/CSharp/Clipper2Lib/Clipper2Lib.csproj b/CSharp/Clipper2Lib/Clipper2Lib.csproj index 9a4f2c5f..7966c056 100644 --- a/CSharp/Clipper2Lib/Clipper2Lib.csproj +++ b/CSharp/Clipper2Lib/Clipper2Lib.csproj @@ -9,7 +9,7 @@ Polygon Clipping and Offsetting Library Clipper2 Clipper2 - http://www.angusj.com/clipper2/Docs/Overview.htm + https://www.angusj.com/clipper2/Docs/Overview.htm Copyright © 2010-2023 git https://github.com/AngusJohnson/Clipper2 diff --git a/CSharp/Clipper2Lib/HashCode.cs b/CSharp/Clipper2Lib/HashCode.cs index d3e6c635..004c7bb3 100644 --- a/CSharp/Clipper2Lib/HashCode.cs +++ b/CSharp/Clipper2Lib/HashCode.cs @@ -42,7 +42,7 @@ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. You can contact the author at : - - xxHash homepage: http://www.xxhash.com + - xxHash homepage: https://www.xxhash.com - xxHash source repository : https://github.com/Cyan4973/xxHash */ @@ -50,7 +50,7 @@ public struct HashCode { private static readonly uint s_seed = GenerateGlobalSeed(); - private const uint Prime1 = 2654435761U; + // private const uint Prime1 = 2654435761U; private const uint Prime2 = 2246822519U; private const uint Prime3 = 3266489917U; private const uint Prime4 = 668265263U; diff --git a/CSharp/USINGZ.TestApp/Program.cs b/CSharp/USINGZ.TestApp/Program.cs index a86af66e..58cfaacd 100644 --- a/CSharp/USINGZ.TestApp/Program.cs +++ b/CSharp/USINGZ.TestApp/Program.cs @@ -1,9 +1,9 @@ /******************************************************************************* * Author : Angus Johnson * * Date : 24 March 2024 * -* Website : http://www.angusj.com * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2024 * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ using System.Collections.Generic; diff --git a/CSharp/Utils/ClipFileIO/Clipper.FileIO.cs b/CSharp/Utils/ClipFileIO/Clipper.FileIO.cs index d210e144..db09dfc3 100644 --- a/CSharp/Utils/ClipFileIO/Clipper.FileIO.cs +++ b/CSharp/Utils/ClipFileIO/Clipper.FileIO.cs @@ -1,9 +1,9 @@ /******************************************************************************* * Author : Angus Johnson * * Date : 16 September 2022 * -* Website : http://www.angusj.com * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2022 * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ using System; @@ -20,17 +20,16 @@ public static Paths64 PathFromStr(string? s) if (s == null) return new Paths64(); Path64 p = new Path64(); Paths64 pp = new Paths64(); - int len = s.Length, i = 0, j; + int len = s.Length, i = 0; while (i < len) { - bool isNeg; while (s[i] < 33 && i < len) i++; if (i >= len) break; //get X ... - isNeg = s[i] == 45; + bool isNeg = s[i] == 45; if (isNeg) i++; if (i >= len || s[i] < 48 || s[i] > 57) break; - j = i + 1; + int j = i + 1; while (j < len && s[j] > 47 && s[j] < 58) j++; if (!long.TryParse(s.Substring(i, j - i), out long x)) break; if (isNeg) x = -x; @@ -80,7 +79,6 @@ public static bool LoadTestNum(string filename, int num, ct = ClipType.Intersection; fillRule = FillRule.EvenOdd; bool result = false; - int GetIdx; if (num < 1) num = 1; caption = ""; area = 0; @@ -141,6 +139,7 @@ public static bool LoadTestNum(string filename, int num, continue; } + int GetIdx; if (s.IndexOf("SUBJECTS_OPEN", StringComparison.Ordinal) == 0) GetIdx = 2; else if (s.IndexOf("SUBJECTS", StringComparison.Ordinal) == 0) GetIdx = 1; else if (s.IndexOf("CLIPS", StringComparison.Ordinal) == 0) GetIdx = 3; @@ -159,9 +158,18 @@ public static bool LoadTestNum(string filename, int num, else return result; continue; } - if (GetIdx == 1) subj.Add(paths[0]); - else if (GetIdx == 2) subj_open.Add(paths[0]); - else clip.Add(paths[0]); + switch (GetIdx) + { + case 1: + subj.Add(paths[0]); + break; + case 2: + subj_open.Add(paths[0]); + break; + default: + clip.Add(paths[0]); + break; + } } } return result; diff --git a/CSharp/Utils/Colors/Clipper.Colors.cs b/CSharp/Utils/Colors/Clipper.Colors.cs index c1eb1d93..cdec9b05 100644 --- a/CSharp/Utils/Colors/Clipper.Colors.cs +++ b/CSharp/Utils/Colors/Clipper.Colors.cs @@ -1,9 +1,9 @@ /******************************************************************************* * Author : Angus Johnson * * Date : 11 February 2023 * -* Website : http://www.angusj.com * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ using System; diff --git a/CSharp/Utils/SVG/Clipper.SVG.Utils.cs b/CSharp/Utils/SVG/Clipper.SVG.Utils.cs index 75f68112..50978837 100644 --- a/CSharp/Utils/SVG/Clipper.SVG.Utils.cs +++ b/CSharp/Utils/SVG/Clipper.SVG.Utils.cs @@ -1,9 +1,9 @@ /******************************************************************************* * Author : Angus Johnson * * Date : 16 September 2022 * -* Website : http://www.angusj.com * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2022 * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ using System.IO; diff --git a/CSharp/Utils/SVG/Clipper.SVG.cs b/CSharp/Utils/SVG/Clipper.SVG.cs index 80f9677f..46599b40 100644 --- a/CSharp/Utils/SVG/Clipper.SVG.cs +++ b/CSharp/Utils/SVG/Clipper.SVG.cs @@ -1,9 +1,9 @@ /******************************************************************************* * Author : Angus Johnson * * Date : 24 March 2024 * -* Website : http://www.angusj.com * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2024 * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ using System; @@ -207,9 +207,7 @@ private RectD GetBounds() if (pt.y < bounds.top) bounds.top = pt.y; if (pt.y > bounds.bottom) bounds.bottom = pt.y; } - if (!IsValidRect(bounds)) - return RectEmpty; - return bounds; + return !IsValidRect(bounds) ? RectEmpty : bounds; } private static string ColorToHtml(uint clr) @@ -281,7 +279,7 @@ public bool SaveToFile(string filename, int maxWidth = 0, int maxHeight = 0, int writer.Write(string.Format(NumberFormatInfo.InvariantInfo, svg_path_format2, ColorToHtml(pi.PenClr), GetAlpha(pi.PenClr), pi.PenWidth)); - if (pi.ShowCoords) + if (!pi.ShowCoords) continue; { writer.Write("\n", coordStyle.FontName, coordStyle.FontSize, ColorToHtml(coordStyle.FontColor)); diff --git a/DLL/CPP_DLL/Clipper2ZDll.vcxproj b/DLL/CPP_DLL/Clipper2ZDll.vcxproj new file mode 100644 index 00000000..cf7f662c --- /dev/null +++ b/DLL/CPP_DLL/Clipper2ZDll.vcxproj @@ -0,0 +1,186 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {fb0a93ce-8e28-4e67-8d05-54b38b218f08} + Clipper2 + 10.0 + Clipper2ZDll + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + Clipper2_Z_64 + ..\..\CPP\Clipper2Lib\include;$(IncludePath) + + + Clipper2_Z_64 + ..\..\CPP\Clipper2Lib\include;$(IncludePath) + + + Clipper2_Z_64 + ..\..\CPP\Clipper2Lib\include;$(IncludePath) + + + Clipper2_Z_64 + ..\..\CPP\Clipper2Lib\include;$(IncludePath) + + + + Level3 + true + true + NotUsing + pch.h + stdcpp17 + stdc17 + USINGZ;%(PreprocessorDefinitions) + + + Windows + true + false + + + + + Level3 + true + true + true + true + NotUsing + pch.h + stdcpp17 + stdc17 + USINGZ;%(PreprocessorDefinitions) + + + Windows + true + true + true + false + + + + + Level3 + true + true + NotUsing + pch.h + stdcpp17 + stdc17 + USINGZ;%(PreprocessorDefinitions) + + + Windows + true + false + + + + + Level3 + true + true + true + true + NotUsing + pch.h + stdcpp17 + stdc17 + USINGZ;%(PreprocessorDefinitions) + + + Windows + true + true + true + false + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DLL/CPP_DLL/Clipper2_DLL.sln b/DLL/CPP_DLL/Clipper2_DLL.sln index 93a82e75..c518302e 100644 --- a/DLL/CPP_DLL/Clipper2_DLL.sln +++ b/DLL/CPP_DLL/Clipper2_DLL.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.3.32901.215 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Clipper2_Export", "Clipper2_Export.vcxproj", "{FB0A93CE-8E28-4E67-8D05-54B38B218F08}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Clipper2_Dll", "Clipper2_Dll.vcxproj", "{FB0A93CE-8E28-4E67-8D05-54B38B218F08}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/DLL/CPP_DLL/Clipper2_Export.vcxproj b/DLL/CPP_DLL/Clipper2_Dll.vcxproj similarity index 96% rename from DLL/CPP_DLL/Clipper2_Export.vcxproj rename to DLL/CPP_DLL/Clipper2_Dll.vcxproj index 4ff8c26f..25be6e08 100644 --- a/DLL/CPP_DLL/Clipper2_Export.vcxproj +++ b/DLL/CPP_DLL/Clipper2_Dll.vcxproj @@ -24,31 +24,32 @@ {fb0a93ce-8e28-4e67-8d05-54b38b218f08} Clipper2 10.0 + Clipper2Dll DynamicLibrary true - v142 + v143 Unicode DynamicLibrary false - v142 + v143 true Unicode DynamicLibrary true - v142 + v143 Unicode DynamicLibrary false - v142 + v143 true Unicode @@ -75,11 +76,11 @@ ..\..\CPP\Clipper2Lib\include;$(IncludePath) - Clipper2_32 + Clipper2_64 ..\..\CPP\Clipper2Lib\include;$(IncludePath) - Clipper2_32 + Clipper2_64 ..\..\CPP\Clipper2Lib\include;$(IncludePath) diff --git a/DLL/CPP_DLL/Clipper2_Z_DLL.sln b/DLL/CPP_DLL/Clipper2_Z_DLL.sln new file mode 100644 index 00000000..429835b6 --- /dev/null +++ b/DLL/CPP_DLL/Clipper2_Z_DLL.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32901.215 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Clipper2ZDll", "Clipper2ZDll.vcxproj", "{FB0A93CE-8E28-4E67-8D05-54B38B218F08}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FB0A93CE-8E28-4E67-8D05-54B38B218F08}.Debug|x64.ActiveCfg = Debug|x64 + {FB0A93CE-8E28-4E67-8D05-54B38B218F08}.Debug|x64.Build.0 = Debug|x64 + {FB0A93CE-8E28-4E67-8D05-54B38B218F08}.Debug|x86.ActiveCfg = Debug|Win32 + {FB0A93CE-8E28-4E67-8D05-54B38B218F08}.Debug|x86.Build.0 = Debug|Win32 + {FB0A93CE-8E28-4E67-8D05-54B38B218F08}.Release|x64.ActiveCfg = Release|x64 + {FB0A93CE-8E28-4E67-8D05-54B38B218F08}.Release|x64.Build.0 = Release|x64 + {FB0A93CE-8E28-4E67-8D05-54B38B218F08}.Release|x86.ActiveCfg = Release|Win32 + {FB0A93CE-8E28-4E67-8D05-54B38B218F08}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BD166825-2BC9-4724-BCF1-F70F90CB74F2} + EndGlobalSection +EndGlobal diff --git a/DLL/CSharp_TestApp/CSharp_TestApp.csproj b/DLL/CSharp_TestApp/CSharp_TestApp.csproj deleted file mode 100644 index fe6a125f..00000000 --- a/DLL/CSharp_TestApp/CSharp_TestApp.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - Exe - net6.0 - enable - enable - x64 - - - diff --git a/DLL/CSharp_TestApp/Program.cs b/DLL/CSharp_TestApp/Program.cs deleted file mode 100644 index b0c491f1..00000000 --- a/DLL/CSharp_TestApp/Program.cs +++ /dev/null @@ -1,291 +0,0 @@ -/******************************************************************************* -* Author : Angus Johnson * -* Date : 29 October 2023 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2023 * -* License : http://www.boost.org/LICENSE_1_0.txt * -*******************************************************************************/ - -using System; -using System.Runtime.InteropServices; - -namespace ClipperDllDemo -{ - - public class Application - { - - // CreateCPaths: The CPaths structure is defined in - // clipper.export.h and is a simple array of long[] or - // double[] that represents a number of path contours. - - static T[]? CreateCPath(T[] coords) - { - int pathLen = coords.Length / 2; - if (pathLen == 0) return null; - int arrayLen = pathLen * 2 + 2; - T[] result = new T[arrayLen]; - result[0] = (T)Convert.ChangeType(pathLen, typeof(T)); - result[1] = (T)Convert.ChangeType(0, typeof(T)); - coords.CopyTo(result, 2); - return result; - } - - static T[] CreateCPaths(List listOfCPath) - { - int pathCount = listOfCPath.Count(); - int arrayLen = 2; - foreach (T[] path in listOfCPath) - arrayLen += path.Length; - T[] result = new T[arrayLen]; - - result[0] = (T)Convert.ChangeType(arrayLen, typeof(T)); - result[1] = (T)Convert.ChangeType(pathCount, typeof(T)); - - int idx = 2; - foreach (T[] cpath in listOfCPath) - { - cpath.CopyTo(result, idx); - idx += cpath.Length; - } - return result; - } - - // or create a cpaths array that contains just 1 path - static T[] CreateCPaths(T[] coords) - { - int pathLen = coords.Length / 2; - int arrayLen = pathLen *2 + 2 + 2; - T[] result = new T[arrayLen]; - - result[0] = (T)Convert.ChangeType(arrayLen, typeof(T)); - result[1] = (T)Convert.ChangeType(1, typeof(T)); // 1 path - - result[2] = (T)Convert.ChangeType(pathLen, typeof(T)); - result[3] = (T)Convert.ChangeType(0, typeof(T)); - - coords.CopyTo(result, 4); - return result; - } - - public static string XyCoordsAsString(T X, T Y, int precision = 0) - { - if (typeof(T) == typeof(long)) // ignore precision - return $"{X},{Y} "; - else - return string.Format($"{{0:F{precision}}},{{1:F{precision}}} ", X, Y); - } - - public static void DisplayCPath(T[] cpaths, ref int idx, string spaceIndent) - { - int vertexCnt = Convert.ToInt32(cpaths[idx]); - idx += 2; - for (int i = 0; i < vertexCnt; i++) - Console.Write(spaceIndent + - XyCoordsAsString(cpaths[idx++], cpaths[idx++], 2)); - Console.Write("\n"); - } - - public static void DisplayCPaths(T[]? cpaths, string spaceIndent) - { - if (cpaths == null) return; - int pathCnt = Convert.ToInt32(cpaths[1]); - int idx = 2; - for (int i = 0; i < pathCnt; i++) - DisplayCPath(cpaths, ref idx, spaceIndent); - } - - // Note: The CPolyTree structure defined in clipper.export.h is - // a simple array of T that contains any number of nested path contours. - - public static void DisplayPolyPath(T[] polypath, - ref int idx, bool isHole, string spaceIndent, int precision) - { - int polyCnt = Convert.ToInt32(polypath[idx++]); - int childCnt = Convert.ToInt32(polypath[idx++]); - string preamble = isHole ? "Hole: " : (spaceIndent == "") ? - "Polygon: " : "Nested Polygon: "; - Console.Write(spaceIndent + preamble); - spaceIndent += " "; - for (int i = 0; i < polyCnt; i++) - Console.Write(XyCoordsAsString(polypath[idx++], polypath[idx++], precision)); - Console.Write("\n"); - for (int i = 0; i < childCnt; i++) - DisplayPolyPath(polypath, ref idx, !isHole, spaceIndent, precision); - } - - public static void DisplayPolytree(T[] polytree, int precision) - { - int cnt = Convert.ToInt32(polytree[1]); - int idx = 2; - for (int i = 0; i < cnt; i++) - DisplayPolyPath(polytree, ref idx, false, " ", precision); - } - - public static T[]? GetArrayFromIntPtr(IntPtr paths) - { - if (paths == IntPtr.Zero) return null; - if (typeof(T) == typeof(long)) - { - long[] len = new long[1]; - Marshal.Copy(paths, len, 0, 1); - long[] res = new long[(int)len[0]]; - Marshal.Copy(paths, res, 0, (int)len[0]); - return res as T[]; - } - else if (typeof(T) == typeof(double)) - { - double[] len = new double[1]; - Marshal.Copy(paths, len, 0, 1); - double[] res = new double[(int)len[0]]; - Marshal.Copy(paths, res, 0, (int)len[0]); - return res as T[]; - } - else return null; - } - - // DLL exported function definitions ///////////////////// - - const string clipperDll = @"..\..\..\..\Clipper2_64.dll"; - - [DllImport(clipperDll, EntryPoint = - "Version", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr Version(); - - [DllImport(clipperDll, EntryPoint = - "BooleanOp64", CallingConvention = CallingConvention.Cdecl)] - static extern Int32 BooleanOp64(byte clipType, byte fillRule, - long[] subjects, long[]? openSubs, long[]? clips, - out IntPtr solution, out IntPtr openSol, bool preserveCollinear, bool reverseSolution); - - [DllImport(clipperDll, EntryPoint = - "BooleanOpD", CallingConvention = CallingConvention.Cdecl)] - static extern Int32 BooleanOpD(byte clipType, byte fillRule, - double[] subjects, double[]? openSubs, double[]? clips, - out IntPtr solution, out IntPtr openSol, Int32 precision, bool preserveCollinear, bool reverseSolution); - - [DllImport(clipperDll, EntryPoint = - "DisposeArray64", CallingConvention = CallingConvention.Cdecl)] - static extern void DisposeArray64(ref IntPtr intptr); - - // DisposeExported(): since all these functions behave identically ... - [DllImport(clipperDll, EntryPoint = - "DisposeArrayD", CallingConvention = CallingConvention.Cdecl)] - static extern void DisposeArrayD(ref IntPtr intptr); - - [DllImport(clipperDll, EntryPoint = - "BooleanOp_PolyTree64", CallingConvention = CallingConvention.Cdecl)] - static extern Int32 BooleanOp_PolyTree64(byte cliptype, - byte fillrule, long[] subjects, long[]? openSubs, long[]? clips, - out IntPtr solTree, out IntPtr openSol, - bool preserve_collinear, bool reverse_solution); - - [DllImport(clipperDll, EntryPoint = - "BooleanOp_PolyTreeD", CallingConvention = CallingConvention.Cdecl)] - static extern Int32 BooleanOp_PolyTreeD(byte cliptype, - byte fillrule, double[] subjects, double[]? openSubs, double[]? clips, - out IntPtr solTree, out IntPtr openSol, Int32 precision, - bool preserve_collinear, bool reverse_solution); - - - public static readonly byte None = 0, Intersection = 1, Union = 2, Difference = 3, Xor = 4; - public static readonly byte EvenOdd = 0, NonZero = 1, Positive = 2, Negative = 3; - - - /// Main Entry //////////////////////////////////////////////////////////// - public static void Main() - { - - //string? ver = Marshal.PtrToStringAnsi(Version()); - //Console.WriteLine(ver +"\n"); - - // test BooleanOp64() /////////////////////////////////////////////////// - Console.WriteLine("BooleanOp64:"); - long[] cSubject = CreateCPaths(new long[] { 0, 0, 100, 0, 100, 100, 0, 100 }); - long[] cClip = CreateCPaths(new long[] { 20, 20, 120, 20, 120, 120, 20, 120 }); - - if (BooleanOp64(Intersection, NonZero, cSubject, - null, cClip, out IntPtr cSol, out IntPtr cSolOpen, false, false) != 0) return; - - long[]? cSolution = GetArrayFromIntPtr(cSol); - // clean up unmanaged memory - DisposeArray64(ref cSol); - DisposeArray64(ref cSolOpen); - - DisplayCPaths(cSolution, " "); - ///////////////////////////////////////////////////////////////////////// - - // test BooleanOpD() //////////////////////////////////////////////////// - Console.WriteLine("BooleanOpD:"); - double[] cSubjectD = CreateCPaths(new double[] { 0, 0, 100, 0, 100, 100, 0, 100 }); - double[] cClipD = CreateCPaths(new double[] { 20, 20, 120, 20, 120, 120, 20, 120 }); - int resultD = BooleanOpD(Intersection, NonZero, cSubjectD, - null, cClipD, out IntPtr cSolD, out IntPtr cSolOpenD, 2, false, false); - if (resultD != 0) return; - double[]? cSolutionD = GetArrayFromIntPtr(cSolD); - // clean up unmanaged memory - DisposeArrayD(ref cSolD); - DisposeArrayD(ref cSolOpenD); - - DisplayCPaths(cSolutionD, " "); - ///////////////////////////////////////////////////////////////////////// - - - - // test BooleanOp_PolyTree64() ////////////////////////////////////////// - Console.WriteLine("BooleanOp_PolyTree64:"); - - List subList = new(5); - for (int i = 1; i < 6; ++i) - subList.Add(CreateCPath(new long[] { - -i * 20, -i * 20, i * 20, -i * 20, i * 20, i * 20, -i * 20, i * 20 })!); - - long[] cSubject3 = CreateCPaths(subList); - long[] cClip3 = CreateCPaths(new long[] { -90, -120, 90, -120, 90, 120, -90, 120 }); - - int result3 = BooleanOp_PolyTree64(Intersection, EvenOdd, cSubject3, null, cClip3, - out IntPtr cSol_pt64, out IntPtr cSolOpen_pt64, false, false); - if (result3 != 0) return; - - long[]? cPolyTree64 = GetArrayFromIntPtr(cSol_pt64); - // clean up unmanaged memory - DisposeArray64(ref cSol_pt64); - DisposeArray64(ref cSolOpen_pt64); - - if (cPolyTree64 == null) return; - DisplayPolytree(cPolyTree64, 2); - ///////////////////////////////////////////////////////////////////////// - - - // test BooleanOp_PolyTreeD() /////////////////////////////////////////// - Console.WriteLine("BooleanOp_PolyTreeD:"); - - List subList2 = new(5); - for (int i = 1; i < 6; ++i) - subList2.Add(CreateCPath(new double[] { - -i * 20, -i * 20, i * 20, -i * 20, i * 20, i * 20, -i * 20, i * 20 })!); - - double[] cSubject4 = CreateCPaths(subList2); - double[] cClip4 = CreateCPaths(new double[] { -90, -120, 90, -120, 90, 120, -90, 120 }); - - int result4 = BooleanOp_PolyTreeD(Intersection, EvenOdd, cSubject4, null, cClip4, - out IntPtr cSol_ptD, out IntPtr cSolOpen_ptD, 2, false, false); - if (result4 != 0) return; - - double[]? cPolyTreeD = GetArrayFromIntPtr(cSol_ptD); - - // clean up unmanaged memory - DisposeArrayD(ref cSol_ptD); - DisposeArrayD(ref cSolOpen_ptD); - - if (cPolyTreeD == null) return; - DisplayPolytree(cPolyTreeD, 2); - ///////////////////////////////////////////////////////////////////////// - - - Console.WriteLine("\nPress any key to exit ... "); - Console.ReadKey(); - } - - } //end Application -} //namespace diff --git a/DLL/CSharp_TestApp2/CSharp_TestApp2.csproj b/DLL/CSharp_TestApp2/CSharp_TestApp2.csproj deleted file mode 100644 index 85e03793..00000000 --- a/DLL/CSharp_TestApp2/CSharp_TestApp2.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - Exe - net6.0 - enable - enable - x64 - - - - - - - - diff --git a/DLL/CSharp_TestApp2/CSharp_TestApp2.sln b/DLL/CSharp_TestApp2/CSharp_TestApp2.sln deleted file mode 100644 index 3b598060..00000000 --- a/DLL/CSharp_TestApp2/CSharp_TestApp2.sln +++ /dev/null @@ -1,63 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.6.33801.468 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharp_TestApp2", "CSharp_TestApp2.csproj", "{3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2Lib", "..\..\CSharp\Clipper2Lib\Clipper2Lib.csproj", "{401DBA71-BB90-495E-9F91-CC495FEE264D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2.SVG", "..\..\CSharp\Utils\SVG\Clipper2.SVG.csproj", "{77C119ED-E0A4-4A76-B858-E7B4AF4DE892}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Debug|Any CPU.ActiveCfg = Debug|x64 - {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Debug|x64.ActiveCfg = Debug|x64 - {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Debug|x64.Build.0 = Debug|x64 - {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Debug|x86.ActiveCfg = Debug|x64 - {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Debug|x86.Build.0 = Debug|x64 - {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Release|Any CPU.ActiveCfg = Release|x64 - {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Release|x64.ActiveCfg = Release|x64 - {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Release|x64.Build.0 = Release|x64 - {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Release|x86.ActiveCfg = Release|x64 - {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Release|x86.Build.0 = Release|x64 - {401DBA71-BB90-495E-9F91-CC495FEE264D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {401DBA71-BB90-495E-9F91-CC495FEE264D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {401DBA71-BB90-495E-9F91-CC495FEE264D}.Debug|x64.ActiveCfg = Debug|Any CPU - {401DBA71-BB90-495E-9F91-CC495FEE264D}.Debug|x64.Build.0 = Debug|Any CPU - {401DBA71-BB90-495E-9F91-CC495FEE264D}.Debug|x86.ActiveCfg = Debug|x86 - {401DBA71-BB90-495E-9F91-CC495FEE264D}.Debug|x86.Build.0 = Debug|x86 - {401DBA71-BB90-495E-9F91-CC495FEE264D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {401DBA71-BB90-495E-9F91-CC495FEE264D}.Release|Any CPU.Build.0 = Release|Any CPU - {401DBA71-BB90-495E-9F91-CC495FEE264D}.Release|x64.ActiveCfg = Release|Any CPU - {401DBA71-BB90-495E-9F91-CC495FEE264D}.Release|x64.Build.0 = Release|Any CPU - {401DBA71-BB90-495E-9F91-CC495FEE264D}.Release|x86.ActiveCfg = Release|x86 - {401DBA71-BB90-495E-9F91-CC495FEE264D}.Release|x86.Build.0 = Release|x86 - {77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Debug|Any CPU.Build.0 = Debug|Any CPU - {77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Debug|x64.ActiveCfg = Debug|Any CPU - {77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Debug|x64.Build.0 = Debug|Any CPU - {77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Debug|x86.ActiveCfg = Debug|x86 - {77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Debug|x86.Build.0 = Debug|x86 - {77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Release|Any CPU.ActiveCfg = Release|Any CPU - {77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Release|Any CPU.Build.0 = Release|Any CPU - {77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Release|x64.ActiveCfg = Release|Any CPU - {77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Release|x64.Build.0 = Release|Any CPU - {77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Release|x86.ActiveCfg = Release|x86 - {77C119ED-E0A4-4A76-B858-E7B4AF4DE892}.Release|x86.Build.0 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {DAE344E0-A107-49C8-B269-1E1896665E6B} - EndGlobalSection -EndGlobal diff --git a/DLL/CSharp_TestApp2/Program.cs b/DLL/CSharp_TestApp2/Program.cs deleted file mode 100644 index 845ac998..00000000 --- a/DLL/CSharp_TestApp2/Program.cs +++ /dev/null @@ -1,184 +0,0 @@ -/******************************************************************************* -* Author : Angus Johnson * -* Date : 26 October 2023 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2023 * -* License : http://www.boost.org/LICENSE_1_0.txt * -*******************************************************************************/ - -using System.Diagnostics; -using System.Runtime.InteropServices; -using Clipper2Lib; - -namespace ClipperDllDemo -{ - public class Application - { - - // Define miscellaneous functions //////////////////////////// - public static void OpenFileWithDefaultApp(string filename) - { - string path = Path.GetFullPath(filename); - if (!File.Exists(path)) return; - Process p = new() { StartInfo = new ProcessStartInfo(path) { UseShellExecute = true } }; - p.Start(); - } - - public static Path64 MakePath(int[] arr) - { - int len = arr.Length / 2; - Path64 p = new(len); - for (int i = 0; i < len; i++) - p.Add(new Point64(arr[i * 2], arr[i * 2 + 1])); - return p; - } - - private static Point64 MakeRandomPt(int maxWidth, int maxHeight, Random rand) - { - long x = rand.Next(maxWidth); - long y = rand.Next(maxHeight); - return new Point64(x, y); - } - - public static Path64 MakeRandomPath(int width, int height, int count, Random rand) - { - Path64 result = new(count); - for (int i = 0; i < count; ++i) - result.Add(MakeRandomPt(width, height, rand)); - return result; - } - - static Path64 GetPath64FromCPath(long[] cpaths, ref int idx) - { - int cnt = (int)cpaths[idx]; idx += 2; - Path64 result = new(cnt); - for (int i = 0; i < cnt; i++) - { - long x = cpaths[idx++]; - long y = cpaths[idx++]; - result.Add(new Point64(x, y)); - } - return result; - } - - static Paths64 GetPaths64FromCPaths(long[] cpaths) - { - int cnt = (int)cpaths[1], idx = 2; - Paths64 result = new(cnt); - for (int i = 0; i < cnt; i++) - result.Add(GetPath64FromCPath(cpaths, ref idx)); - return result; - } - - static long[] CreateCPaths64(Paths64 pp) - { - int len = pp.Count, len2 = 2; - for (int i = 0; i < len; i++) - if (pp[i].Count > 0) - len2 += pp[i].Count * 2 + 2; - long[] result = new long[len2]; - result[0] = 0; - result[1] = len; - int rPos = 2; - for (int i = 0; i < len; i++) - { - len2 = pp[i].Count; - if (len2 == 0) continue; - result[rPos++] = len2; - result[rPos++] = 0; - for (int j = 0; j < len2; j++) - { - result[rPos++] = pp[i][j].X; - result[rPos++] = pp[i][j].Y; - } - } - return result; - } - - public static long[]? GetPathsFromIntPtr(IntPtr paths) - { - if (paths == IntPtr.Zero) return null; - long[] len = new long[1]; - Marshal.Copy(paths, len, 0, 1); - long[] result = new long[len[0]]; - Marshal.Copy(paths, result, 0, (int)len[0]); - return result; - } - - // Define DLL exported functions ///////////////////// - - public const string clipperDll = @"..\..\..\..\Clipper2_64.dll"; - - [DllImport(clipperDll, EntryPoint = "Version", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr Version(); - - [DllImport(clipperDll, EntryPoint = "BooleanOp64", CallingConvention = CallingConvention.Cdecl)] - static extern int BooleanOp64(byte clipType, byte fillRule, - long[] subject, long[]? subOpen, long[]? clip, - out IntPtr solution, out IntPtr solOpen, bool preserveCollinear, bool reverseSolution); - - [DllImport(clipperDll, EntryPoint = "DisposeArray64", CallingConvention = CallingConvention.Cdecl)] - static extern void DisposeArray64(ref IntPtr paths); - - static readonly byte None = 0, Intersection = 1, Union = 2, Difference = 3, Xor = 4; - static readonly byte EvenOdd = 0, NonZero = 1, Positive = 2, Negative = 3; - - public static void Main() - { - - //string? ver = Marshal.PtrToStringAnsi(Version()); - //Console.WriteLine(ver + "\n"); - - long timeMsec; - Random rand = new(); - - //////////////////////////////////////////////////////////////////////// - int edgeCount = 2500; - //////////////////////////////////////////////////////////////////////// - - Paths64 subject = new() { MakeRandomPath(600,400, edgeCount, rand)}; - Paths64 clip = new() { MakeRandomPath(600, 400, edgeCount, rand) }; - - ////////////////////////////////////////////////////////////////////// - // Use Dynamically Linked C++ compiled library (ie use the DLL) - // NB: time will include ALL the overhead of swapping path structures - Stopwatch sw1 = Stopwatch.StartNew(); - long[] cSubject = CreateCPaths64(subject); - long[] cClip = CreateCPaths64(clip); - if (BooleanOp64(Intersection, NonZero, cSubject, - null, cClip, out IntPtr cSol, out IntPtr cSolOpen, false, false) != 0) - return; - - long[]? cSolution = GetPathsFromIntPtr(cSol); - if (cSolution == null) return; - DisposeArray64(ref cSol); - DisposeArray64(ref cSolOpen); - Paths64 solution = GetPaths64FromCPaths(cSolution); - sw1.Stop(); - timeMsec = sw1.ElapsedMilliseconds; - Console.WriteLine($"Time using DLL (C++ code): {timeMsec} ms"); - ////////////////////////////////////////////////////////////////////// - - ////////////////////////////////////////////////////////////////////// - // Use Clipper2's statically linked C# compiled library - Stopwatch sw2 = Stopwatch.StartNew(); - Clipper.Intersect(subject, clip, FillRule.NonZero); - sw2.Stop(); - timeMsec = sw2.ElapsedMilliseconds; - Console.WriteLine($"Time using C# code : {timeMsec} ms"); - ////////////////////////////////////////////////////////////////////// - - string fileName = "../../../clipper2_dll.svg"; - SvgWriter svg = new(FillRule.NonZero); - SvgUtils.AddSubject(svg, subject); - SvgUtils.AddClip(svg, clip); - SvgUtils.AddSolution(svg, solution, false); - svg.SaveToFile(fileName, 800, 600, 20); - OpenFileWithDefaultApp(fileName); - - Console.WriteLine("Press any key to exit ... "); - Console.ReadKey(); - } - - } //end Application -} //namespace diff --git a/DLL/Delphi_TestApp/COMPILE AND ADD Clipper2_64.dll HERE b/DLL/Delphi_TestApp/COMPILE AND ADD Clipper2_64.dll HERE deleted file mode 100644 index e69de29b..00000000 diff --git a/DLL/Delphi_TestApp/Test_DLL.dpr b/DLL/Delphi_TestApp/Test_DLL.dpr deleted file mode 100644 index 7869bd03..00000000 --- a/DLL/Delphi_TestApp/Test_DLL.dpr +++ /dev/null @@ -1,736 +0,0 @@ -program Test_DLL; - -// Make sure that the Clipper2 DLLS are in either -// the OS Path or in the application's folder. - -{$APPTYPE CONSOLE} -{$R *.res} - -uses - Windows, - Math, - ShellApi, - SysUtils, - Clipper in '..\..\Delphi\Clipper2Lib\Clipper.pas', - Clipper.Core in '..\..\Delphi\Clipper2Lib\Clipper.Core.pas', - Clipper.Engine in '..\..\Delphi\Clipper2Lib\Clipper.Engine.pas', - Clipper.SVG in '..\..\Delphi\Utils\Clipper.SVG.pas', - Colors in '..\..\Delphi\Utils\Colors.pas', - Timer in '..\..\Delphi\Utils\Timer.pas'; - -type - CInt64arr = array[0..$FFFF] of Int64; - PCInt64arr = ^CInt64arr; - CPath64 = PCInt64arr; - CPaths64 = PCInt64arr; - CPolyPath64 = PCInt64arr; - CPolytree64 = PCInt64arr; - - CDblarr = array[0..$FFFF] of Double; - PCDblarr = ^CDblarr; - CPathD = PCDblarr; - CPathsD = PCDblarr; - CPolyPathD = PCDblarr; - CPolytreeD = PCDblarr; - -const -{$IFDEF WIN64} - CLIPPER2_DLL = 'Clipper2_64.dll'; -{$ELSE} - CLIPPER2_DLL = 'Clipper2_32.dll'; -{$ENDIF} - - -//////////////////////////////////////////////////////// -// Clipper2 DLL functions -//////////////////////////////////////////////////////// - -function Version(): PAnsiChar; cdecl; - external CLIPPER2_DLL name 'Version'; - -procedure DisposeExportedArray64(var cps: PCInt64arr); cdecl; - external CLIPPER2_DLL name 'DisposeArray64'; -procedure DisposeExportedArrayD(var cp: PCDblarr); cdecl; - external CLIPPER2_DLL name 'DisposeArrayD'; - -function BooleanOp64(cliptype: UInt8; fillrule: UInt8; - const subjects: CPaths64; const subjects_open: CPaths64; - const clips: CPaths64; out solution: CPaths64; - out solution_open: CPaths64; - preserve_collinear: boolean = true; - reverse_solution: boolean = false): integer; cdecl; - external CLIPPER2_DLL name 'BooleanOp64'; -function BooleanOp_PolyTree64(cliptype: UInt8; fillrule: UInt8; - const subjects: CPaths64; const subjects_open: CPaths64; - const clips: CPaths64; out solution: CPolyTree64; - out solution_open: CPaths64; - preserve_collinear: boolean = true; - reverse_solution: boolean = false): integer; cdecl; - external CLIPPER2_DLL name 'BooleanOp_PolyTree64'; - -function BooleanOpD(cliptype: UInt8; fillrule: UInt8; - const subjects: CPathsD; const subjects_open: CPathsD; - const clips: CPathsD; out solution: CPathsD; out solution_open: CPathsD; - precision: integer = 2; - preserve_collinear: boolean = true; - reverse_solution: boolean = false): integer; cdecl; - external CLIPPER2_DLL name 'BooleanOpD'; -function BooleanOp_PolyTreeD(cliptype: UInt8; fillrule: UInt8; - const subjects: CPathsD; const subjects_open: CPathsD; - const clips: CPathsD; out solution: CPolyTreeD; out solution_open: CPathsD; - precision: integer = 2; - preserve_collinear: boolean = true; - reverse_solution: boolean = false): integer; cdecl; - external CLIPPER2_DLL name 'BooleanOp_PolyTreeD'; -function InflatePaths64(const paths: CPaths64; - delta: double; jointype, endtype: UInt8; miter_limit: double = 2.0; - arc_tolerance: double = 0.0; - reverse_solution: Boolean = false): CPaths64; cdecl; - external CLIPPER2_DLL name 'InflatePaths64'; -function InflatePathsD(const paths: CPathsD; - delta: double; jointype, endtype: UInt8; precision: integer = 2; - miter_limit: double = 2.0; arc_tolerance: double = 0.0; - reverse_solution: Boolean = false): CPathsD; cdecl; - external CLIPPER2_DLL name 'InflatePathsD'; - -function RectClip64(const rect: TRect64; const paths: CPaths64; - convexOnly: Boolean = false): CPaths64; cdecl; - external CLIPPER2_DLL name 'RectClip64'; -function RectClipD(const rect: TRectD; const paths: CPathsD; - precision: integer = 2; convexOnly: Boolean = false): CPathsD; cdecl; - external CLIPPER2_DLL name 'RectClipD'; -function RectClipLines64(const rect: TRect64; - const paths: CPaths64): CPaths64; cdecl; - external CLIPPER2_DLL name 'RectClipLines64'; -function RectClipLinesD(const rect: TRectD; - const paths: CPathsD; precision: integer = 2): CPathsD; cdecl; - external CLIPPER2_DLL name 'RectClipLinesD'; - -const - Intersection = 1; Union = 2; Difference =3; Xor_ = 4; - EvenOdd = 0; NonZero = 1; Positive = 2; Negative = 3; - -//////////////////////////////////////////////////////// -// functions related to Clipper2 DLL structures -//////////////////////////////////////////////////////// - -procedure DisposeLocalArray64(cp: PCInt64arr); -begin - FreeMem(cp); -end; - -procedure DisposeLocalArrayD(cp: PCDblarr); -begin - FreeMem(cp); -end; - -//////////////////////////////////////////////////////// -// path format conversion functions -//////////////////////////////////////////////////////// - -function CreateCPaths64(const pp: TPaths64): CPaths64; -var - i,j, len, len2: integer; - v: PInt64; -begin - len := Length(pp); - len2 := 2; - for i := 0 to len -1 do - if Length(pp[i]) > 0 then - inc(len2, Length(pp[i]) *2 + 2); - GetMem(Result, len2 * sizeof(Int64)); - Result[0] := len2; - Result[1] := len; - v := @Result[2]; - for i := 0 to len -1 do - begin - len2 := Length(pp[i]); - if len2 = 0 then continue; - v^ := len2; inc(v); - v^ := 0; inc(v); - for j := 0 to len2 -1 do - begin - v^ := pp[i][j].X; inc(v); - v^ := pp[i][j].Y; inc(v); - end; - end; -end; - -function CreateCPathsD(const pp: TPathsD): CPathsD; -var - i,j, len, len2: integer; - v: PDouble; -begin - len := Length(pp); - len2 := 2; - for i := 0 to len -1 do - if Length(pp[i]) > 0 then - inc(len2, Length(pp[i]) *2 + 2); - GetMem(Result, len2 * sizeof(double)); - Result[0] := len2; - Result[1] := len; - v := @Result[2]; - for i := 0 to len -1 do - begin - len2 := Length(pp[i]); - if len2 = 0 then continue; - v^ := len2; inc(v); - v^ := 0; inc(v); - for j := 0 to len2 -1 do - begin - v^ := pp[i][j].X; inc(v); - v^ := pp[i][j].Y; inc(v); - end; - end; -end; - -function ConvertToTPaths64(cp: CPaths64): TPaths64; -var - i, j, len, len2: integer; - v: PInt64; -begin - Result := nil; - v := PInt64(cp); - inc(v); // ignore array length - len := v^; inc(v); - SetLength(Result, len); - for i := 0 to len -1 do - begin - len2 := v^; inc(v, 2); - SetLength(Result[i], len2); - for j := 0 to len2 -1 do - begin - Result[i][j].X := v^; inc(v); - Result[i][j].Y := v^; inc(v); - end; - end; -end; - -function ConvertToTPathsD(cp: CPathsD): TPathsD; -var - i, j, len, len2: integer; - v: PDouble; -begin - Result := nil; - v := PDouble(cp); - inc(v); // ignore array length - len := Round(cp[1]); inc(v); - SetLength(Result, len); - for i := 0 to len -1 do - begin - len2 := Round(v^); inc(v, 2); - SetLength(Result[i], len2); - for j := 0 to len2 -1 do - begin - Result[i][j].X := v^; inc(v); - Result[i][j].Y := v^; inc(v); - end; - end; -end; - -function GetPolyPath64ArrayLen(const pp: TPolyPath64): integer; -var - i: integer; -begin - Result := 2; // poly_length + child_count - inc(Result, Length(pp.Polygon) * 2); - for i := 0 to pp.Count -1 do - Inc(Result, GetPolyPath64ArrayLen(pp.Child[i])); -end; - -procedure GetPolytreeCountAndCStorageSize(const tree: TPolyTree64; - out cnt: integer; out arrayLen: integer); -begin - cnt := tree.Count; // nb: top level count only - arrayLen := GetPolyPath64ArrayLen(tree); -end; - -procedure CreateCPolyPathD(const pp: TPolyPath64; - var v: PDouble; scale: double); -var - i, len: integer; -begin - len := Length(pp.Polygon); - v^ := len; inc(v); - v^ := pp.Count; inc(v); - for i := 0 to len -1 do - begin - v^ := pp.Polygon[i].x * scale; - v^ := pp.Polygon[i].y * scale; - end; - for i := 0 to pp.Count -1 do - CreateCPolyPathD(pp.Child[i], v, scale); -end; - - -function CreateCPolyTreeD(const tree: TPolyTree64; scale: double): CPolyTreeD; -var - i, cnt, arrayLen: integer; - v: PDouble; -begin - Result := nil; - GetPolytreeCountAndCStorageSize(tree, cnt, arrayLen); - if cnt = 0 then Exit; - // allocate storage - GetMem(Result, arrayLen * SizeOf(double)); - - v := PDouble(Result); - v^ := arrayLen; inc(v); - v^ := tree.Count; inc(v); - for i := 0 to tree.Count - 1 do - CreateCPolyPathD(tree.Child[i], v, scale); -end; - -function CreatePolyPath64FromCPolyPath(var v: PInt64; owner: TPolyPath64): Boolean; -var - i, childCount, len: integer; - path: TPath64; - newOwner: TPolyPath64; -begin - Result := false; - len := v^; inc(v); //polygon length - childCount := v^; inc(v); - if (len = 0) then Exit; - SetLength(path, len); - for i := 0 to len -1 do - begin - path[i].X := v^; inc(v); - path[i].Y := v^; inc(v); - end; - newOwner := TPolyPath64(owner.AddChild(path)); - for i := 0 to childCount -1 do - if not CreatePolyPath64FromCPolyPath(v, newOwner) then Exit; - Result := true; -end; - -function BuildPolyTree64FromCPolyTree(tree: CPolyTree64; outTree: TPolyTree64): Boolean; -var - v: PInt64; - i, childCount: integer; -begin - Result := false; - outTree.Clear(); - v := PInt64(tree); - inc(v); //skip array size - childCount := v^; inc(v); - for i := 0 to childCount -1 do - if not CreatePolyPath64FromCPolyPath(v, outTree) then Exit; - Result := true; -end; - -function CreatePolyPathDFromCPolyPath(var v: PDouble; owner: TPolyPathD): Boolean; -var - i, len, childCount: integer; - path: TPathD; - newOwner: TPolyPathD; -begin - Result := false; - len := Round(v^); inc(v); - childCount := Round(v^); inc(v); - if (len = 0) then Exit; - SetLength(path, len); - for i := 0 to len -1 do - begin - path[i].X := v^; inc(v); - path[i].Y := v^; inc(v); - end; - newOwner := TPolyPathD(owner.AddChild(path)); - for i := 0 to childCount -1 do - if not CreatePolyPathDFromCPolyPath(v, newOwner) then Exit; - Result := true; -end; - -function BuildPolyTreeDFromCPolyTree(tree: CPolyTreeD; outTree: TPolyTreeD): Boolean; -var - v: PDouble; - i, childCount: integer; -begin - Result := false; - outTree.Clear(); - v := PDouble(tree); - inc(v); // ignore array size - childCount := Round(v^); inc(v); - for i := 0 to childCount -1 do - if not CreatePolyPathDFromCPolyPath(v, outTree) then Exit; - Result := true; -end; - -//////////////////////////////////////////////////////// -// miscellaneous functions -//////////////////////////////////////////////////////// - -function MakePath64(vals: array of Int64): TPath64; -var - i, len: integer; -begin - len := Length(vals) div 2; - SetLength(Result, len); - for i := 0 to len -1 do - begin - Result[i].X := vals[i*2]; - Result[i].Y := vals[i*2 +1]; - end; -end; - -function MakePathD(vals: array of double): TPathD; -var - i, len: integer; -begin - len := Length(vals) div 2; - SetLength(Result, len); - for i := 0 to len -1 do - begin - Result[i].X := vals[i*2]; - Result[i].Y := vals[i*2 +1]; - end; -end; - -function MakeRandomPath(maxWidth, maxHeight, count: Integer; - margin: Integer = 10): TPath64; -var - i: Integer; -begin - setlength(Result, count); - for i := 0 to count -1 do with Result[i] do - begin - X := Random(maxWidth - 2 * margin) + margin; - Y := Random(maxHeight - 2 * margin) + margin; - end; -end; - -function MakeRandomPathD(maxWidth, maxHeight, count: Integer; - margin: Integer = 10): TPathD; -var - i: Integer; -begin - setlength(Result, count); - for i := 0 to count -1 do with Result[i] do - begin - X := Random(maxWidth - 2 * margin) + margin; - Y := Random(maxHeight - 2 * margin) + margin; - end; -end; - -procedure ShowSvgImage(const svgFilename: string); -begin - ShellExecute(0, 'open',PChar(svgFilename), nil, nil, SW_SHOW); -end; - -const - displayWidth = 600; - displayHeight = 400; - -procedure DisplaySVG(const sub, subo, clp, sol, solo: TPathsD; - const svgName: string; width: integer = displayWidth; - height: integer = displayHeight); overload; -var - svg: TSvgWriter; -begin - svg := TSvgWriter.Create(frNonZero); - try - AddSubject(svg, sub); - AddOpenSubject(svg, subo); - AddClip(svg, clp); - AddSolution(svg, sol); - AddOpenSolution(svg, solo); - SaveSvg(svg, svgName, width, height); - ShowSvgImage(svgName); - finally - svg.Free; - end; -end; - -procedure DisplaySVG(const sub, subo, clp, sol, solo: TPaths64; - const svgName: string; width: integer = displayWidth; - height: integer = displayHeight); overload; -var - svg: TSvgWriter; -begin - svg := TSvgWriter.Create(frNonZero); - try - AddSubject(svg, sub); - AddOpenSubject(svg, subo); - AddClip(svg, clp); - - AddSolution(svg, sol); - AddOpenSolution(svg, solo); - SaveSvg(svg, svgName, width, height); - ShowSvgImage(svgName); - finally - svg.Free; - end; -end; - -//////////////////////////////////////////////////////// -// test procedures -//////////////////////////////////////////////////////// - -procedure Test_Version(); -begin - Write(#10'Clipper2 DLL version: '); - WriteLn(Version); -end; - -procedure Test_BooleanOp64(edgeCnt: integer); -var - sub, clp: TPaths64; - csub_local, cclp_local: CPaths64; - csol_extern, csolo_extern: CPaths64; -begin - // setup - csolo_extern := nil; - WriteLn(#10'Testing BooleanOp64'); - SetLength(sub, 1); - sub[0] := MakeRandomPath(displayWidth, displayHeight, edgeCnt); - SetLength(clp, 1); - clp[0] := MakeRandomPath(displayWidth, displayHeight, edgeCnt); - // convert paths into DLL structures (will require local clean up) - csub_local := CreateCPaths64(sub); - cclp_local := CreateCPaths64(clp); - - // do the DLL operation - BooleanOp64(Intersection, NonZero, - csub_local, nil, cclp_local, - csol_extern, csolo_extern); - - DisplaySVG(sub, nil, clp, - ConvertToTPaths64(csol_extern), nil, 'BooleanOp64.svg'); - - // clean up - DisposeLocalArray64(csub_local); - DisposeLocalArray64(cclp_local); - DisposeExportedArray64(csol_extern); - DisposeExportedArray64(csolo_extern); -end; - -procedure Test_BooleanOpD(edgeCnt: integer); -var - sub, clp: TPathsD; - csub_local, cclp_local: CPathsD; - csol_extern, csolo_extern: CPathsD; -begin - // setup - csolo_extern := nil; - WriteLn(#10'Testing BooleanOpD'); - SetLength(sub, 1); - sub[0] := MakeRandomPathD(displayWidth, displayHeight, edgeCnt); - SetLength(clp, 1); - clp[0] := MakeRandomPathD(displayWidth, displayHeight, edgeCnt); - // convert paths into DLL structures (will require local clean up) - csub_local := CreateCPathsD(sub); - cclp_local := CreateCPathsD(clp); - - // do the DLL operation - BooleanOpD(Uint8(TClipType.ctIntersection), - Uint8(TFillRule.frNonZero), - csub_local, nil, cclp_local, - csol_extern, csolo_extern); - - // optionally display result on the console - //WriteCPaths64(csol_extern); - - DisplaySVG(sub, nil, clp, - ConvertToTPathsD(csol_extern), nil, 'BooleanOpD.svg'); - - DisposeLocalArrayD(csub_local); - DisposeLocalArrayD(cclp_local); - DisposeExportedArrayD(csol_extern); - DisposeExportedArrayD(csolo_extern); -end; - -procedure Test_BooleanOp_Polytree64(edgeCnt: integer); -var - sub, clp, sol: TPaths64; - csub_local, cclp_local: CPaths64; - csol_extern: CPolyTree64; - tree: TPolyTree64; - csol_open_extern: CPaths64; -begin - // setup - WriteLn(#10'Testing BooleanOp_PolyTree64'); - SetLength(sub, 1); - sub[0] := MakeRandomPath(displayWidth, displayHeight, edgeCnt); - SetLength(clp, 1); - clp[0] := MakeRandomPath(displayWidth, displayHeight, edgeCnt); - // convert paths into DLL structures (will require local clean up) - csub_local := CreateCPaths64(sub); - cclp_local := CreateCPaths64(clp); - - // do the DLL operation - BooleanOp_PolyTree64(Intersection, NonZero, - csub_local, nil, cclp_local, csol_extern, csol_open_extern); - - tree := TPolyTree64.Create; - try - BuildPolyTree64FromCPolyTree(csol_extern, tree); - sol := PolyTreeToPaths64(tree); - finally - tree.Free; - end; - DisposeExportedArray64(csol_extern); - DisposeExportedArray64(csol_open_extern); - - DisposeLocalArray64(csub_local); - DisposeLocalArray64(cclp_local); - - // finally, display and clean up - DisplaySVG(sub, nil, clp, sol, nil, 'BooleanOp_PolyTree64.svg'); -end; - -procedure Test_BooleanOp_PolytreeD(edgeCnt: integer); -var - sub, clp, sol: TPathsD; - csub_local, cclp_local: CPathsD; - csol_extern: CPolyTreeD; - tree: TPolyTreeD; - csol_open_extern: CPathsD; -begin - // setup - WriteLn(#10'Testing BooleanOp_PolyTreeD'); - SetLength(sub, 1); - sub[0] := MakeRandomPathD(displayWidth, displayHeight, edgeCnt); - SetLength(clp, 1); - clp[0] := MakeRandomPathD(displayWidth, displayHeight, edgeCnt); - // convert paths into DLL structures (will require local clean up) - csub_local := CreateCPathsD(sub); - cclp_local := CreateCPathsD(clp); - - // do the DLL operation - BooleanOp_PolyTreeD(Intersection, NonZero, - csub_local, nil, cclp_local, csol_extern, csol_open_extern); - - tree := TPolyTreeD.Create; - try - BuildPolyTreeDFromCPolyTree(csol_extern, tree); - sol := PolyTreeToPathsD(tree); - finally - tree.Free; - end; - DisposeExportedArrayD(csol_extern); - DisposeExportedArrayD(csol_open_extern); - - DisposeLocalArrayD(csub_local); - DisposeLocalArrayD(cclp_local); - - // finally, display and clean up - DisplaySVG(sub, nil, clp, sol, nil, 'BooleanOp_PolyTreeD.svg'); -end; - -procedure Test_InflatePathsD(edgeCnt: integer; delta: double); -var - sub: TPathsD; - csub_local: CPathsD; - csol_extern: CPathsD; - csolo_extern: CPathsD; -begin - // setup - WriteLn(#10'Testing InflatePaths64'); - SetLength(sub, 1); - sub[0] := MakeRandomPathD(displayWidth, displayHeight, edgeCnt); - // convert path into required DLL structure (also requires local clean up) - csub_local := CreateCPathsD(sub); - - // and because offsetting self-intersecting paths is unpredictable - // we must remove self-intersection via a union operation - BooleanOpD(Uint8(TClipType.ctUnion), - Uint8(TFillRule.frNonZero), csub_local, nil, nil, - csol_extern, csolo_extern); - - // now do the DLL operation - csol_extern := InflatePathsD(csol_extern, delta, - UInt8(TJoinType.jtRound), UInt8(TEndType.etPolygon), 2, 4); - - // optionally display result on the console - //WriteCPaths64(csol_extern); - - DisplaySVG(sub, nil, nil, - ConvertToTPathsD(csol_extern), nil, 'InflatePathsD.svg'); - - DisposeLocalArrayD(csub_local); - DisposeExportedArrayD(csol_extern); - DisposeExportedArrayD(csolo_extern); -end; - -procedure Test_RectClipD(edgeCount: integer); -var - rec_margin: Integer; - sub, clp, sol: TPathsD; - csub_local: CPathsD; - csol_extern: CPathsD; - rec: TRectD; -begin - WriteLn(#10'Testing RectClipD:'); - - rec_margin := Min(displayWidth,displayHeight) div 4; - rec.Left := rec_margin; - rec.Top := rec_margin; - rec.Right := displayWidth - rec_margin; - rec.Bottom := displayHeight -rec_margin; - - SetLength(sub, 1); - sub[0] := MakeRandomPathD(displayWidth, displayHeight, edgeCount); - csub_local := CreateCPathsD(sub); - - csol_extern := RectClipD(rec, csub_local, 2, true); - sol := ConvertToTPathsD(csol_extern); - DisposeLocalArrayD(csub_local); - DisposeExportedArrayD(csol_extern); - - SetLength(clp, 1); - clp[0] := rec.AsPath; - DisplaySVG(sub, nil, clp, sol, - nil, 'RectClipD.svg', displayWidth,displayHeight); -end; - -procedure Test_RectClipLines64(edgeCnt: integer); -var - sub, clp: TPaths64; - csub_local: CPaths64; - csolo_extern: CPaths64; - rec: TRect64; -begin - // setup - WriteLn(#10'Testing RectClipLines64:'); - SetLength(sub, 1); - - sub[0] := MakeRandomPath(displayWidth, displayHeight, edgeCnt); - csub_local := CreateCPaths64(sub); - - rec.Left := 80; - rec.Top := 80; - rec.Right := displayWidth - 80; - rec.Bottom := displayHeight -80; - - // do the DLL operation - csolo_extern := RectClipLines64(rec, csub_local); - - SetLength(clp, 1); - clp[0] := rec.AsPath; - - DisplaySVG(nil, sub, clp, nil, - ConvertToTPaths64(csolo_extern), 'RectClipLines64.svg'); - - DisposeLocalArray64(csub_local); - DisposeExportedArray64(csolo_extern); -end; - -//////////////////////////////////////////////////////// -// main entry here -//////////////////////////////////////////////////////// - -//var -// s: string; -begin - Randomize; - Test_Version(); - Test_BooleanOp64(25); - Test_BooleanOpD(25); - Test_BooleanOp_Polytree64(15); - Test_BooleanOp_PolytreeD(25); - Test_InflatePathsD(20, -10); // edgeCount, offsetDist - Test_RectClipD(7); - Test_RectClipLines64(25); - -// WriteLn(#10'Press Enter to quit.'); -// ReadLn(s); -end. diff --git a/DLL/Delphi_TestApp/Test_DLL.dproj b/DLL/Delphi_TestApp/Test_DLL.dproj deleted file mode 100644 index c84a1231..00000000 --- a/DLL/Delphi_TestApp/Test_DLL.dproj +++ /dev/null @@ -1,138 +0,0 @@ - - - {E2CCB41D-87F4-433F-9DAA-E38EF73B29B3} - Test_DLL.dpr - True - Debug - 2 - Console - None - 19.2 - Win64 - - - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - false - false - false - false - false - 00400000 - Test_DLL - 3081 - CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= - System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) - $(BDS)\bin\delphi_PROJECTICON.ico - $(BDS)\bin\delphi_PROJECTICNS.icns - - - Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) - Debug - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - 1033 - - - RELEASE;$(DCC_Define) - 0 - false - 0 - - - DEBUG;$(DCC_Define) - false - true - - - - MainSource - - - - - - - - Cfg_2 - Base - - - Base - - - Cfg_1 - Base - - - - Delphi.Personality.12 - - - - - Test_DLL.dpr - - - - True - - - - - Test_DLL.exe - true - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - - 12 - - - - - diff --git a/DLL/CSharp_TestApp/COMPILE AND ADD Clipper2_64.dll HERE b/DLL/TEST_APPS/CSharp_TestApps/COPY Clipper2 DLLs HERE similarity index 100% rename from DLL/CSharp_TestApp/COMPILE AND ADD Clipper2_64.dll HERE rename to DLL/TEST_APPS/CSharp_TestApps/COPY Clipper2 DLLs HERE diff --git a/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp/CSharp_TestApp.csproj b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp/CSharp_TestApp.csproj new file mode 100644 index 00000000..2bfe6ff7 --- /dev/null +++ b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp/CSharp_TestApp.csproj @@ -0,0 +1,19 @@ + + + + Exe + net6.0 + enable + enable + x64 + + + + $(DefineConstants);USINGZ + + + + $(DefineConstants);USINGZ + + + diff --git a/DLL/CSharp_TestApp/CSharp_TestApp.sln b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp/CSharp_TestApp.sln similarity index 100% rename from DLL/CSharp_TestApp/CSharp_TestApp.sln rename to DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp/CSharp_TestApp.sln diff --git a/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp/Clipper2DllCore.cs b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp/Clipper2DllCore.cs new file mode 100644 index 00000000..df8124b4 --- /dev/null +++ b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp/Clipper2DllCore.cs @@ -0,0 +1,392 @@ +using System.Drawing; +using System.Runtime.InteropServices; +using static Clipper2Dll.Clipper2DllCore; + +using ztype = System.Int64; +using Point64 = Clipper2Dll.Clipper2DllCore.Point; +using PointD = Clipper2Dll.Clipper2DllCore.Point; +using Rect64 = Clipper2Dll.Clipper2DllCore.Rect; +using RectD = Clipper2Dll.Clipper2DllCore.Rect; + +namespace Clipper2Dll +{ + + public static class Clipper2DllCore + { + ///////////////////////////////////////////////////////////////////////// + // (Very abbreviated) Clipper2 structures + ///////////////////////////////////////////////////////////////////////// + public enum FillRule { EvenOdd, NonZero, Positive, Negative }; + public enum ClipType { None, Intersection, Union, Difference, Xor }; + +#if USINGZ + public static long VERTEX_FIELD_CNT = 3; + + public struct Point + { + public T X; + public T Y; + public ztype Z; + + public Point(Point pt) + { + X = pt.X; + Y = pt.Y; + Z = pt.Z; + } + + public Point(T x, T y, ztype z = 0) + { + X = x; + Y = y; + Z = z; + } + } +#else + + public static long VERTEX_FIELD_CNT = 2; + + public struct Point + { + public T X; + public T Y; + + public Point(Point pt) + { + X = pt.X; + Y = pt.Y; + } + + public Point(T x, T y) + { + X = x; + Y = y; + } + } +#endif + + public struct Rect where T : IComparable + { + public T left; + public T top; + public T right; + public T bottom; + public Rect(T l, T t, T r, T b) + { + left = l; + top = t; + right = r; + bottom = b; + } + public bool IsEmpty() + { + return left.CompareTo(right) >= 0 || top.CompareTo(bottom) >= 0; + } + public double Width() + { + if (IsEmpty()) return 0; + dynamic r = right; + dynamic l = left; + return r - l; + } + public double Height() + { + if (IsEmpty()) return 0; + dynamic b = bottom; + dynamic t = top; + return b - t; + } + } + + public static RectD InvalidRectD = + new RectD(double.MaxValue, double.MaxValue, -double.MaxValue, -double.MaxValue); + + ///////////////////////////////////////////////////////////////////////// + // Clipper2 DLL - exported functions + ///////////////////////////////////////////////////////////////////////// + +#if USINGZ + const string clipperDll = @"..\..\..\..\..\Clipper2_Z_64.dll"; +#else + const string clipperDll = @"..\..\..\..\..\Clipper2_64.dll"; +#endif + + [DllImport(clipperDll, EntryPoint = + "Version", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr Version(); + + [DllImport(clipperDll, EntryPoint = + "BooleanOp64", CallingConvention = CallingConvention.Cdecl)] + public static extern Int32 BooleanOp64(byte clipType, byte fillRule, + long[] subjects, long[]? openSubs, long[]? clips, + out IntPtr solution, out IntPtr openSol, bool preserveCollinear, bool reverseSolution); + + [DllImport(clipperDll, EntryPoint = + "BooleanOpD", CallingConvention = CallingConvention.Cdecl)] + public static extern Int32 BooleanOpD(byte clipType, byte fillRule, + double[] subjects, double[]? openSubs, double[]? clips, + out IntPtr solution, out IntPtr openSol, Int32 precision, bool preserveCollinear, bool reverseSolution); + + [DllImport(clipperDll, EntryPoint = + "DisposeArray64", CallingConvention = CallingConvention.Cdecl)] + public static extern void DisposeArray64(ref IntPtr intptr); + + // DisposeExported(): since all these functions behave identically ... + [DllImport(clipperDll, EntryPoint = + "DisposeArrayD", CallingConvention = CallingConvention.Cdecl)] + public static extern void DisposeArrayD(ref IntPtr intptr); + + [DllImport(clipperDll, EntryPoint = + "BooleanOp_PolyTree64", CallingConvention = CallingConvention.Cdecl)] + public static extern Int32 BooleanOp_PolyTree64(byte cliptype, + byte fillrule, long[] subjects, long[]? openSubs, long[]? clips, + out IntPtr solTree, out IntPtr openSol, + bool preserve_collinear, bool reverse_solution); + + [DllImport(clipperDll, EntryPoint = + "BooleanOp_PolyTreeD", CallingConvention = CallingConvention.Cdecl)] + public static extern Int32 BooleanOp_PolyTreeD(byte cliptype, + byte fillrule, double[] subjects, double[]? openSubs, double[]? clips, + out IntPtr solTree, out IntPtr openSol, Int32 precision, + bool preserve_collinear, bool reverse_solution); + + +#if USINGZ + public delegate void DLLZCallback64(Point64 e1bot, Point64 e1top, Point64 e2bot, Point64 e2top, ref Point64 ip); + + [DllImport(clipperDll, EntryPoint = + "SetZCallback64", CallingConvention = CallingConvention.Cdecl)] + public static extern void SetZCallback64(DLLZCallback64 callback); + + public delegate void DLLZCallbackD(PointD e1bot, PointD e1top, PointD e2bot, PointD e2top, ref PointD ip); + + [DllImport(clipperDll, EntryPoint = + "SetZCallbackD", CallingConvention = CallingConvention.Cdecl)] + public static extern void SetZCallbackD(DLLZCallbackD callback); +#endif + + ///////////////////////////////////////////////////////////////////////// + // Clipper2 DLL support functions + ///////////////////////////////////////////////////////////////////////// + + // CreateCPaths: The CPaths structure is defined in + // clipper.export.h and is a simple array of long[] or + // double[] that represents a number of path contours. + + public static T[]? CreateCPath(T[] coords) + { + long pathLen = coords.Length / VERTEX_FIELD_CNT; + if (pathLen == 0) return null; + long arrayLen = pathLen * VERTEX_FIELD_CNT + 2; + T[] result = new T[arrayLen]; + result[0] = (T)Convert.ChangeType(pathLen, typeof(T)); + result[1] = (T)Convert.ChangeType(0, typeof(T)); + coords.CopyTo(result, 2); + return result; + } + + public static T[] CreateCPathsFromCPathList(List listOfCPath) + { + int pathCount = listOfCPath.Count(); + int arrayLen = 2; + foreach (T[] path in listOfCPath) + arrayLen += path.Length; + T[] result = new T[arrayLen]; + + result[0] = (T)Convert.ChangeType(arrayLen, typeof(T)); + result[1] = (T)Convert.ChangeType(pathCount, typeof(T)); + + int idx = 2; + foreach (T[] cpath in listOfCPath) + { + cpath.CopyTo(result, idx); + idx += cpath.Length; + } + return result; + } + + // or create a cpaths array that contains just 1 path + public static T[] CreateCPathsFromCoords(T[] coords) + { + long pathLen = coords.Length / VERTEX_FIELD_CNT; + long arrayLen = pathLen * VERTEX_FIELD_CNT + 4; + T[] result = new T[arrayLen]; + + result[0] = (T)Convert.ChangeType(arrayLen, typeof(T)); + result[1] = (T)Convert.ChangeType(1, typeof(T)); // 1 path + result[2] = (T)Convert.ChangeType(pathLen, typeof(T)); + result[3] = (T)Convert.ChangeType(0, typeof(T)); + + coords.CopyTo(result, 4); + return result; + } + +#if USINGZ + public static string VertexAsString(T X, T Y, T Z, int precision) + { + if (typeof(T) == typeof(long)) // ignore precision + return $"{X},{Y},{Z} "; + else + return string.Format($"{{0:F{precision}}},{{1:F{precision}}},{{2:F{precision}}} ", X, Y, Z); + } +#else + public static string VertexAsString(T X, T Y, int precision) + { + if (typeof(T) == typeof(long)) // ignore precision + return $"{X},{Y} "; + else + return string.Format($"{{0:F{precision}}},{{1:F{precision}}} ", X, Y); + } +#endif + + public static void LogCPath(T[] cpaths, ref int idx, string spaceIndent) + { + int vertexCnt = Convert.ToInt32(cpaths[idx]); + idx += 2; + for (int i = 0; i < vertexCnt; i++) +#if USINGZ + Console.Write(spaceIndent + VertexAsString(cpaths[idx++], cpaths[idx++], cpaths[idx++], 2)); +#else + Console.Write(spaceIndent + VertexAsString(cpaths[idx++], cpaths[idx++], 2)); +#endif + Console.Write("\n"); + } + + public static void LogCPaths(T[]? cpaths, string spaceIndent) + { + if (cpaths == null) return; + int pathCnt = Convert.ToInt32(cpaths[1]); + int idx = 2; + for (int i = 0; i < pathCnt; i++) + LogCPath(cpaths, ref idx, spaceIndent); + } + + // Note: The CPolyTree structure defined in clipper.export.h is + // a simple array of T that contains any number of nested path contours. + + private static void CPolypathCounter(T[] cpolypath, + ref long idx, ref int pathCount, ref long arrayLen) + { + // returns the vertex count of each contained polygon + int vertexCount = Convert.ToInt32(cpolypath[idx++]); + int childCount = Convert.ToInt32(cpolypath[idx++]); + arrayLen += vertexCount * VERTEX_FIELD_CNT + 2; + pathCount++; + idx += vertexCount * VERTEX_FIELD_CNT; + for (int i = 0; i < childCount; i++) + CPolypathCounter(cpolypath, ref idx, ref pathCount, ref arrayLen); + } + private static void ConvertCPolypathToCPaths(T[] cpolypath, ref long idx, + T[] result, ref int resultIdx) + { + T vertexCount = cpolypath[idx++]!; + int vertCnt = (int)Convert.ChangeType(vertexCount, typeof(int)); + int childCount = Convert.ToInt32(cpolypath[idx++]); + result[resultIdx++] = vertexCount; + result[resultIdx++] = (T)Convert.ChangeType(0, typeof(T)); + // parse path + for (int i = 0; i < vertCnt; i++) + { + result[resultIdx++] = cpolypath[idx++]; // x + result[resultIdx++] = cpolypath[idx++]; // y +#if USINGZ + result[resultIdx++] = cpolypath[idx++]; // z +#endif + } + // parse children + for (int i = 0; i < childCount; i++) + ConvertCPolypathToCPaths(cpolypath, ref idx, result, ref resultIdx); + } + + public static void ConvertCPolytreeToCPaths(T[] cpolytree, out T[] result) + { + if (cpolytree == null) + { + result = new T[0]; + return; + } + int topPathsCount = Convert.ToInt32(cpolytree[1]); + long idx = 2; + int pathCount = 0; + long arrayLen = 2; + for (int i = 0; i < topPathsCount; i++) + CPolypathCounter(cpolytree, ref idx, ref pathCount, ref arrayLen); + result = new T[arrayLen]; + result[0] = (T)Convert.ChangeType(arrayLen, typeof(T)); + result[1] = (T)Convert.ChangeType(pathCount, typeof(T)); + int resultIdx = 2; + idx = 2; + for (int i = 0; i < topPathsCount; i++) + ConvertCPolypathToCPaths(cpolytree, ref idx, result, ref resultIdx); + } + + public static void LogPolyPath(T[] polypath, + ref int idx, bool isHole, string spaceIndent, int precision) + { + int polyCnt = Convert.ToInt32(polypath[idx++]); + int childCnt = Convert.ToInt32(polypath[idx++]); + string preamble = isHole ? "Hole: " : (spaceIndent == "") ? + "Polygon: " : "Nested Polygon: "; + Console.Write(spaceIndent + preamble); + spaceIndent += " "; + for (int i = 0; i < polyCnt; i++) +#if USINGZ + Console.Write(VertexAsString(polypath[idx++], polypath[idx++], polypath[idx++], precision)); +#else + Console.Write(VertexAsString(polypath[idx++], polypath[idx++], precision)); +#endif + Console.Write("\n"); + for (int i = 0; i < childCnt; i++) + LogPolyPath(polypath, ref idx, !isHole, spaceIndent, precision); + } + + public static void LogPolytree(T[] polytree, int precision) + { + int cnt = Convert.ToInt32(polytree[1]); + int idx = 2; + for (int i = 0; i < cnt; i++) + LogPolyPath(polytree, ref idx, false, " ", precision); + } + + public static T[]? GetArrayFromIntPtr(IntPtr paths) + { + if (paths == IntPtr.Zero) return null; + if (typeof(T) == typeof(long)) + { + long[] len = new long[1]; + Marshal.Copy(paths, len, 0, 1); + long[] res = new long[(int)len[0]]; + Marshal.Copy(paths, res, 0, (int)len[0]); + return res as T[]; + } + else if (typeof(T) == typeof(double)) + { + double[] len = new double[1]; + Marshal.Copy(paths, len, 0, 1); + double[] res = new double[(int)len[0]]; + Marshal.Copy(paths, res, 0, (int)len[0]); + return res as T[]; + } + else return null; + } + + public static void ConvertArrayOfLongs(long[] longs, out double[] result) + { + result = new double[longs.Length]; + for (int i = 0; i < longs.Length; i++) + result[i] = longs[i]; + } + + public static double StaticCastLongToDouble(long val) + { + byte[] b = BitConverter.GetBytes(val); + return BitConverter.ToDouble(b, 0); + } + public static long StaticCastDoubleToLong(double val) + { + byte[] b = BitConverter.GetBytes(val); + return BitConverter.ToInt64(b, 0); + } + + } +} diff --git a/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp/Clipper2DllSvg.cs b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp/Clipper2DllSvg.cs new file mode 100644 index 00000000..84bb74b6 --- /dev/null +++ b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp/Clipper2DllSvg.cs @@ -0,0 +1,469 @@ +using System.Diagnostics; +using System.Globalization; +using static Clipper2Dll.Clipper2DllCore; + +using RectD = Clipper2Dll.Clipper2DllCore.Rect; + +namespace Clipper2Dll +{ + public class SvgWriter + { + private static RectD rectMax = InvalidRectD; + + public static RectD RectEmpty = new RectD(0, 0, 0, 0); + internal static bool IsValidRect(RectD rec) + { + return rec.right >= rec.left && rec.bottom >= rec.top; + } + + private readonly struct CoordStyle + { + public readonly string FontName; + public readonly int FontSize; + public readonly uint FontColor; + public CoordStyle(string fontname, int fontsize, uint fontcolor) + { + FontName = fontname; + FontSize = fontsize; + FontColor = fontcolor; + } + } + private readonly struct TextInfo + { + public readonly string text; + public readonly int fontSize; + public readonly uint fontColor; + public readonly double posX; + public readonly double posY; + public TextInfo(string text, double x, double y, + int fontsize = 12, uint fontcolor = 0xFF000000) + { + this.text = text; + posX = x; + posY = y; + fontSize = fontsize; + fontColor = fontcolor; + } + } + + private readonly struct CircleInfo + { + public readonly double centerX; + public readonly double centerY; + public readonly double radius; + public readonly uint brushClr; + public readonly uint penClr; + public readonly uint penWidth; + public CircleInfo(double centerX, double centerY, + double radius, uint brushClr, uint penClr, uint penWidth) + { + this.centerX = centerX; + this.centerY = centerY; + this.radius = radius; + this.brushClr = brushClr; + this.penClr = penClr; + this.penWidth = penWidth; + } + } + + private readonly struct PolyInfo + { + public readonly double[] paths; + public readonly uint BrushClr; + public readonly uint PenClr; + public readonly double PenWidth; + public readonly bool ShowCoords; + public readonly bool IsOpen; + public PolyInfo(double[] paths, uint brushcolor, uint pencolor, + double penwidth, bool showcoords = false, bool isopen = false) + { + this.paths = new double[paths.Length]; + paths.CopyTo(this.paths, 0); + BrushClr = brushcolor; + PenClr = pencolor; + PenWidth = penwidth; + ShowCoords = showcoords; + IsOpen = isopen; + } + } + + public FillRule FillRule { get; set; } + private readonly List PolyInfoList = new List(); + private readonly List textInfos = new List(); + private readonly List circleInfos = new List(); + private readonly CoordStyle coordStyle; + + private const string svg_header = "\n" + + "\n\n"; + private const string svg_path_format = "\"\n style=\"fill:{0};" + + " fill-opacity:{1:f2}; fill-rule:{2}; stroke:{3};" + + " stroke-opacity:{4:f2}; stroke-width:{5:f2};\"/>\n\n"; + private const string svg_path_format2 = "\"\n style=\"fill:none; stroke:{0};" + + "stroke-opacity:{1:f2}; stroke-width:{2:f2};\"/>\n\n"; + + public SvgWriter(FillRule fillrule = FillRule.EvenOdd, + string coordFontName = "Verdana", int coordFontsize = 9, uint coordFontColor = 0xFF000000) + { + coordStyle = new CoordStyle(coordFontName, coordFontsize, coordFontColor); + FillRule = fillrule; + } + + public void ClearPaths() + { + PolyInfoList.Clear(); + } + + public void ClearText() + { + textInfos.Clear(); + } + + public void ClearCircles() + { + circleInfos.Clear(); + } + + public void ClearAll() + { + PolyInfoList.Clear(); + textInfos.Clear(); + circleInfos.Clear(); + } + public void AddClosedPath(long[] path, uint brushColor, + uint penColor, double penWidth, bool showCoords = false) + { + long[] tmp = new long[path.Count() +2]; + path.CopyTo(tmp, 2); + tmp[0] = tmp.Length; + tmp[1] = 1; + AddClosedPaths(tmp, brushColor, penColor, penWidth, showCoords); + } + + public void AddClosedPath(double[] path, uint brushColor, + uint penColor, double penWidth, bool showCoords = false) + { + double[] tmp = new double[path.Length + 2]; + path.CopyTo(tmp, 2); + tmp[0] = tmp.Length; + tmp[1] = 1; + AddClosedPaths(tmp, brushColor, penColor, penWidth, showCoords); + } + + public void AddClosedPaths(long[] paths, uint brushColor, + uint penColor, double penWidth, bool showCoords = false) + { + if (paths.Length == 0) return; + double[] pathsD; + ConvertArrayOfLongs(paths, out pathsD); + PolyInfoList.Add(new PolyInfo(pathsD, brushColor, penColor, penWidth, showCoords, false)); + } + + public void AddClosedPaths(double[] paths, uint brushColor, + uint penColor, double penWidth, bool showCoords = false) + { + if (paths.Length == 0) return; + PolyInfoList.Add(new PolyInfo(paths, brushColor, penColor, penWidth, showCoords, false)); + } + + public void AddOpenPath(long[] path, uint penColor, + double penWidth, bool showCoords = false) + { + long[] tmp = new long[path.Length + 2]; + path.CopyTo(tmp, 2); + tmp[0] = tmp.Length; + tmp[1] = 1; + AddOpenPaths(tmp, penColor, penWidth, showCoords); + } + + public void AddOpenPath(double[] path, uint penColor, + double penWidth, bool showCoords = false) + { + double[] tmp = new double[path.Length + 2]; + path.CopyTo(tmp, 2); + tmp[0] = tmp.Length; + tmp[1] = 1; + AddOpenPaths(tmp, penColor, penWidth, showCoords); + } + + public void AddOpenPaths(long[] paths, + uint penColor, double penWidth, bool showCoords = false) + { + if (paths.Length == 0) return; + double[] pathsD; + ConvertArrayOfLongs(paths, out pathsD); + PolyInfoList.Add(new PolyInfo(pathsD, 0x0, penColor, penWidth, showCoords, true)); + } + + public void AddOpenPaths(double[] paths, uint penColor, + double penWidth, bool showCoords = false) + { + if (paths.Length == 0) return; + PolyInfoList.Add(new PolyInfo(paths, + 0x0, penColor, penWidth, showCoords, true)); + } + + + public void AddText(string cap, double posX, double posY, int fontSize, uint fontClr = 0xFF000000) + { + textInfos.Add(new TextInfo(cap, posX, posY, fontSize, fontClr)); + } + + public void AddCircle(double centerX, double centerY, + double radius, uint brushClr, uint penClr, uint penWidth) + { + circleInfos.Add(new CircleInfo(centerX, centerY, radius, brushClr, penClr, penWidth)); + } + + private RectD GetBounds() + { + RectD bounds = new RectD(double.MaxValue, double.MaxValue, -double.MaxValue, -double.MaxValue); + foreach (PolyInfo pi in PolyInfoList) + { + int idx = 2; + long pathsCnt = Convert.ToInt64(pi.paths[1]); + for (int i = 0; i < pathsCnt; i++) + { + long pathLen = Convert.ToInt64(pi.paths[idx]); + idx += 2; + for (int j = 0; j < pathLen; j++) + { + double x = pi.paths[idx++]; + double y = pi.paths[idx++]; +#if USINGZ + idx++; +#endif + if (x < bounds.left) bounds.left = x; + if (x > bounds.right) bounds.right = x; + if (y < bounds.top) bounds.top = y; + if (y > bounds.bottom) bounds.bottom = y; + } + + } + } + if (!IsValidRect(bounds)) return RectEmpty; + return bounds; + } + + private static string ColorToHtml(uint clr) + { + return '#' + (clr & 0xFFFFFF).ToString("X6"); + } + + private static float GetAlpha(uint clr) + { + return ((float)(clr >> 24) / 255); + } + + public bool SaveToFile(string filename, int maxWidth = 0, int maxHeight = 0, int margin = -1) + { + if (margin < 0) margin = 20; + RectD bounds = GetBounds(); + if (bounds.IsEmpty()) return false; + + double scale = 1.0; + if (maxWidth > 0 && maxHeight > 0) + scale = Math.Min( + (maxWidth - margin * 2) / bounds.Width(), + (maxHeight - margin * 2) / bounds.Height()); + + long offsetX = margin - (long)(bounds.left * scale); + long offsetY = margin - (long)(bounds.top * scale); + + StreamWriter writer; + try + { + writer = new StreamWriter(filename); + } + catch + { + return false; + } + + if (maxWidth <= 0 || maxHeight <= 0) + writer.Write(svg_header, (bounds.right - bounds.left) + margin * 2, + (bounds.bottom - bounds.top) + margin * 2); + else + writer.Write(svg_header, maxWidth, maxHeight); + + foreach (PolyInfo pi in PolyInfoList) + { + writer.Write(" \n", + coordStyle.FontName, coordStyle.FontSize, ColorToHtml(coordStyle.FontColor)); + + idx = 2; + pathsCnt = Convert.ToInt64(pi.paths[1]); + for (int i = 0; i < pathsCnt; i++) + { + long pathLen = Convert.ToInt64(pi.paths[idx]); + idx += 2; + for (int j = 1; j < pathLen; j++) + { + double x = pi.paths[idx++]; + double y = pi.paths[idx++]; +#if USINGZ + double z = pi.paths[idx++]; + writer.Write("{2:f2},{3:f2},{4}\n", + (x * scale + offsetX), (y * scale + offsetY), x, y, z); +#else + writer.Write("{2:f2},{3:f2}\n", + (x * scale + offsetX), (y * scale + offsetY), x, y); +#endif + } + } + writer.Write("\n\n"); + } + } + + foreach (CircleInfo circInfo in circleInfos) + { + writer.Write("\n \n\n", + circInfo.centerX * scale + offsetX, circInfo.centerY * scale + offsetY, + circInfo.radius * scale, ColorToHtml(circInfo.penClr), circInfo.penWidth, + ColorToHtml(circInfo.brushClr), GetAlpha(circInfo.brushClr)); + } + + + foreach (TextInfo captionInfo in textInfos) + { + writer.Write("\n", + captionInfo.fontSize, ColorToHtml(captionInfo.fontColor)); + writer.Write("{2}\n\n", + captionInfo.posX * scale + offsetX, captionInfo.posY * scale + offsetY, captionInfo.text); + } + + writer.Write("\n"); + writer.Close(); + return true; + } + } //end SvgWriter + +public static class SvgWriterUtils + { + public static void AddCaption(SvgWriter svg, string caption, int x, int y) + { + svg.AddText(caption, x, y, 14); + } + + public static void AddSubject(SvgWriter svg, long[] path) + { + svg.AddClosedPath(path, 0x1800009C, 0xAAB3B3DA, 0.8); + } + public static void AddSubject(SvgWriter svg, double[] path) + { + svg.AddClosedPath(path, 0x1800009C, 0xAAB3B3DA, 0.8); + } + + public static void AddSubjects(SvgWriter svg, long[] paths) + { + svg.AddClosedPaths(paths, 0x1800009C, 0xAAB3B3DA, 0.8); + } + public static void AddOpenSubjects(SvgWriter svg, long[] paths) + { + svg.AddOpenPaths(paths, 0xAAB3B3DA, 0.8); + } + + public static void AddSubjects(SvgWriter svg, double[] paths) + { + svg.AddClosedPaths(paths, 0x1800009C, 0xAAB3B3DA, 0.8); + } + + public static void AddOpenSubjects(SvgWriter svg, double[] paths) + { + svg.AddOpenPaths(paths, 0xAAB3B3DA, 1.2); + } + + public static void AddClip(SvgWriter svg, long[] path) + { + svg.AddClosedPath(path, 0x129C0000, 0xCCFFA07A, 0.8); + } + + public static void AddClip(SvgWriter svg, double[] path) + { + svg.AddClosedPath(path, 0x129C0000, 0xCCFFA07A, 0.8); + } + + public static void AddClips(SvgWriter svg, long[] paths) + { + svg.AddClosedPaths(paths, 0x129C0000, 0xCCFFA07A, 0.8); + } + + public static void AddClips(SvgWriter svg, double[] paths) + { + svg.AddClosedPaths(paths, 0x129C0000, 0xCCFFA07A, 0.8); + } + + public static void AddSolution(SvgWriter svg, long[] paths, + bool show_coords, bool is_closed = true, bool is_joined = true) + { + svg.AddClosedPaths(paths, 0x4080ff9C, 0xFF003300, 1.5, show_coords); + } + + public static void AddOpenSolution(SvgWriter svg, long[] paths, bool show_coords) + { + svg.AddOpenPaths(paths, 0xFF003300, 2.2, show_coords); + } + + public static void AddSolution(SvgWriter svg, double[] paths, bool show_coords) + { + svg.AddClosedPaths(paths, 0x4080ff9C, 0xFF003300, 1.5, show_coords); + } + + public static void AddOpenSolution(SvgWriter svg, double[] paths, bool show_coords) + { + svg.AddOpenPaths(paths, 0xFF003300, 2.2, show_coords); + } + + public static void OpenFileWithDefaultApp(string filename) + { + string path = Path.GetFullPath(filename); + if (!File.Exists(path)) return; + Process p = new Process() { StartInfo = new ProcessStartInfo(path) { UseShellExecute = true } }; + p.Start(); + } + + } //end SvgWriterUtils +} diff --git a/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp/Program.cs b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp/Program.cs new file mode 100644 index 00000000..05fbe00b --- /dev/null +++ b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp/Program.cs @@ -0,0 +1,253 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 29 October 2023 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2023 * +* License : https://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +using Clipper2Dll; +using System.Globalization; +using System.Runtime.InteropServices; +using static Clipper2Dll.Clipper2DllCore; +using static Clipper2Dll.SvgWriterUtils; + +using ztype = System.Int64; +using Point64 = Clipper2Dll.Clipper2DllCore.Point; +using PointD = Clipper2Dll.Clipper2DllCore.Point; + +namespace ClipperDllDemo +{ + + public class Application + { + +#if USINGZ + private static long counter = 0; + private static void MyCallBack64(Point64 e1bot, Point64 e1top, Point64 e2bot, Point64 e2top, ref Point64 ip) + { + ip.Z = counter++; + } + + private static void MyCallBackD(PointD e1bot, PointD e1top, PointD e2bot, PointD e2top, ref PointD ip) + { + ip.Z = StaticCastDoubleToLong((double)counter++); + } +#endif + + private static void DrawSVG(string filename, long[]? cSubjects, long[]? cClips, long[]? cSolution) + { + SvgWriter svg = new SvgWriter(); + if (cSubjects != null) + AddSubjects(svg, cSubjects); + if (cClips != null) + AddClips(svg, cClips); + if (cSolution != null) + { + AddSolution(svg, cSolution, false); +#if USINGZ + long pathsCnt = cSolution[1]; + long idx = 2; + for (long i = 0; i < pathsCnt; i++) + { + long pathLen = cSolution[idx]; + idx += 2; + for (long j = 0; j < pathLen; j++) + { + long x = cSolution[idx++]; + long y = cSolution[idx++]; + long z = cSolution[idx++]; + if (z <= 10) continue; + svg.AddText(string.Format(NumberFormatInfo.InvariantInfo, "{0}", z), x +1, y - 3, 9); + svg.AddCircle(x, y, 3, 0x40FFFF00, 0xFF000000, 2); + } + } +#endif + } + svg.SaveToFile(filename, 400, 400); + OpenFileWithDefaultApp(filename); + } + + private static void DrawSVG(string filename, double[] cSubjects, double[] cClips, double[] cSolution) + { + SvgWriter svg = new SvgWriter(); + AddSubjects(svg, cSubjects); + AddClips(svg, cClips); + if (cSolution.Length > 0) + { + AddSolution(svg, cSolution, false); +#if USINGZ + long pathsCnt = Convert.ToInt64(cSolution[1]); + long idx = 2; + for (long i = 0; i < pathsCnt; i++) + { + long pathLen = Convert.ToInt64(cSolution[idx]); + idx += 2; + for (long j = 0; j < pathLen; j++) + { + double x = cSolution[idx++]; + double y = cSolution[idx++]; + double z = cSolution[idx++]; + if (z <= 10) continue; + svg.AddText(string.Format(NumberFormatInfo.InvariantInfo, "{0}", z), x + 1, y - 3, 9); + svg.AddCircle(x, y, 3, 0x40FFFF00, 0xFF000000, 2); + } + } +#endif + } + svg.SaveToFile(filename, 400, 400); + OpenFileWithDefaultApp(filename); + } + + /// Main Entry //////////////////////////////////////////////////////////// + public static void Main() + { + //string? ver = Marshal.PtrToStringAnsi(Version()); + //Console.WriteLine(ver + "\n"); + +#if USINGZ + SetZCallback64(MyCallBack64); + SetZCallbackD(MyCallBackD); +#endif + + ///////////////////////////////////////////////////////////////////////// + // test BooleanOp64() /////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +#if USINGZ + long[] cSubject = CreateCPathsFromCoords(new long[] { 0,0,1, 100,0,2, 100,100,3, 0,100,4 }); + long[] cClip = CreateCPathsFromCoords(new long[] { 20,20,1, 120,20,2, 120,120,3, 20,120,4 }); + counter = 11; +#else + long[] cSubject = CreateCPathsFromCoords(new long[] { 0,0, 100,0, 100,100, 0,100 }); + long[] cClip = CreateCPathsFromCoords(new long[] { 20,20, 120,20, 120,120, 20,120 }); +#endif + + if (BooleanOp64((int)ClipType.Intersection, (int)FillRule.NonZero, cSubject, + null, cClip, out IntPtr cSol, out IntPtr cSolOpen, false, false) != 0) return; + + long[]? cSolution = GetArrayFromIntPtr(cSol); + + //Console.WriteLine("BooleanOp64:"); + //LogCPaths(cSolution, " "); + + // clean up unmanaged memory + DisposeArray64(ref cSol); + DisposeArray64(ref cSolOpen); + + DrawSVG(@"..\..\..\rectangles.svg", cSubject, cClip, cSolution); + + ///////////////////////////////////////////////////////////////////////// + // test BooleanOpD() //////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +#if USINGZ + double[] cSubjectD = CreateCPathsFromCoords(new double[] { 0,0,1, 100,0,2, 100,100,3, 0,100,4 }); + double[] cClipD = CreateCPathsFromCoords(new double[] { 20,20,1, 120,20,2, 120,120,3, 20,120,4 }); + counter = 21; +#else + double[] cSubjectD = CreateCPathsFromCoords(new double[] { 0,0, 100,0, 100,100, 0,100 }); + double[] cClipD = CreateCPathsFromCoords(new double[] { 20,20, 120,20, 120,120, 20,120 }); +#endif + int resultD = BooleanOpD((int)ClipType.Intersection, (int)FillRule.NonZero, cSubjectD, + null, cClipD, out IntPtr cSolD, out IntPtr cSolOpenD, 2, false, false); + if (resultD != 0) return; + double[]? cSolutionD = GetArrayFromIntPtr(cSolD); + + //Console.WriteLine("BooleanOpD:"); + //LogCPaths(cSolutionD, " "); + + // clean up unmanaged memory + DisposeArrayD(ref cSolD); + DisposeArrayD(ref cSolOpenD); + + DrawSVG(@"..\..\..\rectangles2.svg", cSubjectD, cClipD, cSolutionD!); + + ///////////////////////////////////////////////////////////////////////// + // test BooleanOp_PolyTree64() ////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // create arrays of x,y coords that define 5 successively + // larger rectangles centered on the origin ... + List cRectangles = new(5); +#if USINGZ + for (int i = 1; i < 6; ++i) + cRectangles.Add(CreateCPath(new long[] { + -i*20,-i*20,1, i*20,-i*20,2, i*20,i*20,3, -i*20,i*20,4 })!); + long[] cSubjects = CreateCPathsFromCPathList(cRectangles); + long[] cClips = CreateCPathsFromCoords( + new long[] { -90,-120,11, 90,-120,12, 90,120,13, -90,120,14 }); + counter = 31; +#else + for (int i = 1; i < 6; ++i) + cRectangles.Add(CreateCPath(new long[] { + -i*20,-i*20, i*20,-i*20, i*20,i*20, -i*20,i*20 })!); + long[] cSubjects = CreateCPathsFromCPathList(cRectangles); + long[] cClips = CreateCPathsFromCoords(new long[] { -90,-120, 90,-120, 90,120, -90,120 }); +#endif + + int result3 = BooleanOp_PolyTree64((int)ClipType.Intersection, + (int)FillRule.EvenOdd, cSubjects, null, cClips, + out IntPtr cSol_pt64, out IntPtr cSolOpen_pt64, false, false); + if (result3 != 0) return; + + long[]? cPolyTree64 = GetArrayFromIntPtr(cSol_pt64); + // clean up unmanaged memory + DisposeArray64(ref cSol_pt64); + DisposeArray64(ref cSolOpen_pt64); + + if (cPolyTree64 == null) return; + //Console.WriteLine("BooleanOp_PolyTree64:"); + //LogPolytree(cPolyTree64, 2); + + ConvertCPolytreeToCPaths(cPolyTree64, out long[] solution5); + DrawSVG(@"..\..\..\polytree64.svg", cSubjects, cClips, solution5); + + ///////////////////////////////////////////////////////////////////////// + // test BooleanOp_PolyTreeD() /////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + List subList2 = new(5); +#if USINGZ + for (int i = 1; i < 6; ++i) + subList2.Add(CreateCPath(new double[] { + -i*20,-i*20,1, i*20,-i*20,2, i*20,i*20,3, -i*20,i*20,4 })!); + double[] cSubject4 = CreateCPathsFromCPathList(subList2); + double[] cClip4 = CreateCPathsFromCoords(new double[] { -90, -120, 11, 90, -120, 12, 90, 120, 13, -90, 120, 14 }); + counter = 41; +#else + for (int i = 1; i < 6; ++i) + subList2.Add(CreateCPath(new double[] { + -i*20,-i*20, i*20,-i*20, i*20,i*20, -i*20,i*20 })!); + double[] cSubject4 = CreateCPathsFromCPathList(subList2); + double[] cClip4 = CreateCPathsFromCoords(new double[] { -90,-120, 90,-120, 90,120, -90,120 }); +#endif + + int result4 = BooleanOp_PolyTreeD((int)ClipType.Intersection, + (int)FillRule.EvenOdd, cSubject4, null, cClip4, + out IntPtr cSol_ptD, out IntPtr cSolOpen_ptD, 2, false, false); + if (result4 != 0) return; + + double[]? cPolyTreeD = GetArrayFromIntPtr(cSol_ptD); + + // clean up unmanaged memory + DisposeArrayD(ref cSol_ptD); + DisposeArrayD(ref cSolOpen_ptD); + + if (cPolyTreeD == null) return; + + //Console.WriteLine("BooleanOp_PolyTreeD:"); + //LogPolytree(cPolyTreeD, 2); + + ConvertCPolytreeToCPaths(cPolyTreeD, out double[] solution6); + DrawSVG(@"..\..\..\polytreeD.svg", cSubject4, cClip4, solution6); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + //Console.WriteLine("\nPress any key to exit ... "); + //Console.ReadKey(); + } + + } //end Application +} //end namespace diff --git a/DLL/CSharp_TestApp/polytree_sample.png b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp/polytree_sample.png similarity index 100% rename from DLL/CSharp_TestApp/polytree_sample.png rename to DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp/polytree_sample.png diff --git a/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp2/CSharp_TestApp2.csproj b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp2/CSharp_TestApp2.csproj new file mode 100644 index 00000000..553504d4 --- /dev/null +++ b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp2/CSharp_TestApp2.csproj @@ -0,0 +1,23 @@ + + + + Exe + net6.0 + enable + enable + x64 + + + + $(DefineConstants);USINGZ + + + + $(DefineConstants);USINGZ + + + + + + + diff --git a/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp2/CSharp_TestApp2.sln b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp2/CSharp_TestApp2.sln new file mode 100644 index 00000000..7df98794 --- /dev/null +++ b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp2/CSharp_TestApp2.sln @@ -0,0 +1,49 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33801.468 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharp_TestApp2", "CSharp_TestApp2.csproj", "{3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2Lib", "..\..\..\..\CSharp\Clipper2Lib\Clipper2Lib.csproj", "{6170CB42-C763-4C12-A9E5-7D90E6953615}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Debug|Any CPU.ActiveCfg = Debug|x64 + {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Debug|x64.ActiveCfg = Debug|x64 + {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Debug|x64.Build.0 = Debug|x64 + {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Debug|x86.ActiveCfg = Debug|x64 + {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Debug|x86.Build.0 = Debug|x64 + {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Release|Any CPU.ActiveCfg = Release|x64 + {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Release|x64.ActiveCfg = Release|x64 + {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Release|x64.Build.0 = Release|x64 + {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Release|x86.ActiveCfg = Release|x64 + {3D611FB4-7D15-4FD4-9EE4-D02E0D37F424}.Release|x86.Build.0 = Release|x64 + {6170CB42-C763-4C12-A9E5-7D90E6953615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6170CB42-C763-4C12-A9E5-7D90E6953615}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6170CB42-C763-4C12-A9E5-7D90E6953615}.Debug|x64.ActiveCfg = Debug|Any CPU + {6170CB42-C763-4C12-A9E5-7D90E6953615}.Debug|x64.Build.0 = Debug|Any CPU + {6170CB42-C763-4C12-A9E5-7D90E6953615}.Debug|x86.ActiveCfg = Debug|x86 + {6170CB42-C763-4C12-A9E5-7D90E6953615}.Debug|x86.Build.0 = Debug|x86 + {6170CB42-C763-4C12-A9E5-7D90E6953615}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6170CB42-C763-4C12-A9E5-7D90E6953615}.Release|Any CPU.Build.0 = Release|Any CPU + {6170CB42-C763-4C12-A9E5-7D90E6953615}.Release|x64.ActiveCfg = Release|Any CPU + {6170CB42-C763-4C12-A9E5-7D90E6953615}.Release|x64.Build.0 = Release|Any CPU + {6170CB42-C763-4C12-A9E5-7D90E6953615}.Release|x86.ActiveCfg = Release|x86 + {6170CB42-C763-4C12-A9E5-7D90E6953615}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DAE344E0-A107-49C8-B269-1E1896665E6B} + EndGlobalSection +EndGlobal diff --git a/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp2/Clipper2DllCore.cs b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp2/Clipper2DllCore.cs new file mode 100644 index 00000000..df8124b4 --- /dev/null +++ b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp2/Clipper2DllCore.cs @@ -0,0 +1,392 @@ +using System.Drawing; +using System.Runtime.InteropServices; +using static Clipper2Dll.Clipper2DllCore; + +using ztype = System.Int64; +using Point64 = Clipper2Dll.Clipper2DllCore.Point; +using PointD = Clipper2Dll.Clipper2DllCore.Point; +using Rect64 = Clipper2Dll.Clipper2DllCore.Rect; +using RectD = Clipper2Dll.Clipper2DllCore.Rect; + +namespace Clipper2Dll +{ + + public static class Clipper2DllCore + { + ///////////////////////////////////////////////////////////////////////// + // (Very abbreviated) Clipper2 structures + ///////////////////////////////////////////////////////////////////////// + public enum FillRule { EvenOdd, NonZero, Positive, Negative }; + public enum ClipType { None, Intersection, Union, Difference, Xor }; + +#if USINGZ + public static long VERTEX_FIELD_CNT = 3; + + public struct Point + { + public T X; + public T Y; + public ztype Z; + + public Point(Point pt) + { + X = pt.X; + Y = pt.Y; + Z = pt.Z; + } + + public Point(T x, T y, ztype z = 0) + { + X = x; + Y = y; + Z = z; + } + } +#else + + public static long VERTEX_FIELD_CNT = 2; + + public struct Point + { + public T X; + public T Y; + + public Point(Point pt) + { + X = pt.X; + Y = pt.Y; + } + + public Point(T x, T y) + { + X = x; + Y = y; + } + } +#endif + + public struct Rect where T : IComparable + { + public T left; + public T top; + public T right; + public T bottom; + public Rect(T l, T t, T r, T b) + { + left = l; + top = t; + right = r; + bottom = b; + } + public bool IsEmpty() + { + return left.CompareTo(right) >= 0 || top.CompareTo(bottom) >= 0; + } + public double Width() + { + if (IsEmpty()) return 0; + dynamic r = right; + dynamic l = left; + return r - l; + } + public double Height() + { + if (IsEmpty()) return 0; + dynamic b = bottom; + dynamic t = top; + return b - t; + } + } + + public static RectD InvalidRectD = + new RectD(double.MaxValue, double.MaxValue, -double.MaxValue, -double.MaxValue); + + ///////////////////////////////////////////////////////////////////////// + // Clipper2 DLL - exported functions + ///////////////////////////////////////////////////////////////////////// + +#if USINGZ + const string clipperDll = @"..\..\..\..\..\Clipper2_Z_64.dll"; +#else + const string clipperDll = @"..\..\..\..\..\Clipper2_64.dll"; +#endif + + [DllImport(clipperDll, EntryPoint = + "Version", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr Version(); + + [DllImport(clipperDll, EntryPoint = + "BooleanOp64", CallingConvention = CallingConvention.Cdecl)] + public static extern Int32 BooleanOp64(byte clipType, byte fillRule, + long[] subjects, long[]? openSubs, long[]? clips, + out IntPtr solution, out IntPtr openSol, bool preserveCollinear, bool reverseSolution); + + [DllImport(clipperDll, EntryPoint = + "BooleanOpD", CallingConvention = CallingConvention.Cdecl)] + public static extern Int32 BooleanOpD(byte clipType, byte fillRule, + double[] subjects, double[]? openSubs, double[]? clips, + out IntPtr solution, out IntPtr openSol, Int32 precision, bool preserveCollinear, bool reverseSolution); + + [DllImport(clipperDll, EntryPoint = + "DisposeArray64", CallingConvention = CallingConvention.Cdecl)] + public static extern void DisposeArray64(ref IntPtr intptr); + + // DisposeExported(): since all these functions behave identically ... + [DllImport(clipperDll, EntryPoint = + "DisposeArrayD", CallingConvention = CallingConvention.Cdecl)] + public static extern void DisposeArrayD(ref IntPtr intptr); + + [DllImport(clipperDll, EntryPoint = + "BooleanOp_PolyTree64", CallingConvention = CallingConvention.Cdecl)] + public static extern Int32 BooleanOp_PolyTree64(byte cliptype, + byte fillrule, long[] subjects, long[]? openSubs, long[]? clips, + out IntPtr solTree, out IntPtr openSol, + bool preserve_collinear, bool reverse_solution); + + [DllImport(clipperDll, EntryPoint = + "BooleanOp_PolyTreeD", CallingConvention = CallingConvention.Cdecl)] + public static extern Int32 BooleanOp_PolyTreeD(byte cliptype, + byte fillrule, double[] subjects, double[]? openSubs, double[]? clips, + out IntPtr solTree, out IntPtr openSol, Int32 precision, + bool preserve_collinear, bool reverse_solution); + + +#if USINGZ + public delegate void DLLZCallback64(Point64 e1bot, Point64 e1top, Point64 e2bot, Point64 e2top, ref Point64 ip); + + [DllImport(clipperDll, EntryPoint = + "SetZCallback64", CallingConvention = CallingConvention.Cdecl)] + public static extern void SetZCallback64(DLLZCallback64 callback); + + public delegate void DLLZCallbackD(PointD e1bot, PointD e1top, PointD e2bot, PointD e2top, ref PointD ip); + + [DllImport(clipperDll, EntryPoint = + "SetZCallbackD", CallingConvention = CallingConvention.Cdecl)] + public static extern void SetZCallbackD(DLLZCallbackD callback); +#endif + + ///////////////////////////////////////////////////////////////////////// + // Clipper2 DLL support functions + ///////////////////////////////////////////////////////////////////////// + + // CreateCPaths: The CPaths structure is defined in + // clipper.export.h and is a simple array of long[] or + // double[] that represents a number of path contours. + + public static T[]? CreateCPath(T[] coords) + { + long pathLen = coords.Length / VERTEX_FIELD_CNT; + if (pathLen == 0) return null; + long arrayLen = pathLen * VERTEX_FIELD_CNT + 2; + T[] result = new T[arrayLen]; + result[0] = (T)Convert.ChangeType(pathLen, typeof(T)); + result[1] = (T)Convert.ChangeType(0, typeof(T)); + coords.CopyTo(result, 2); + return result; + } + + public static T[] CreateCPathsFromCPathList(List listOfCPath) + { + int pathCount = listOfCPath.Count(); + int arrayLen = 2; + foreach (T[] path in listOfCPath) + arrayLen += path.Length; + T[] result = new T[arrayLen]; + + result[0] = (T)Convert.ChangeType(arrayLen, typeof(T)); + result[1] = (T)Convert.ChangeType(pathCount, typeof(T)); + + int idx = 2; + foreach (T[] cpath in listOfCPath) + { + cpath.CopyTo(result, idx); + idx += cpath.Length; + } + return result; + } + + // or create a cpaths array that contains just 1 path + public static T[] CreateCPathsFromCoords(T[] coords) + { + long pathLen = coords.Length / VERTEX_FIELD_CNT; + long arrayLen = pathLen * VERTEX_FIELD_CNT + 4; + T[] result = new T[arrayLen]; + + result[0] = (T)Convert.ChangeType(arrayLen, typeof(T)); + result[1] = (T)Convert.ChangeType(1, typeof(T)); // 1 path + result[2] = (T)Convert.ChangeType(pathLen, typeof(T)); + result[3] = (T)Convert.ChangeType(0, typeof(T)); + + coords.CopyTo(result, 4); + return result; + } + +#if USINGZ + public static string VertexAsString(T X, T Y, T Z, int precision) + { + if (typeof(T) == typeof(long)) // ignore precision + return $"{X},{Y},{Z} "; + else + return string.Format($"{{0:F{precision}}},{{1:F{precision}}},{{2:F{precision}}} ", X, Y, Z); + } +#else + public static string VertexAsString(T X, T Y, int precision) + { + if (typeof(T) == typeof(long)) // ignore precision + return $"{X},{Y} "; + else + return string.Format($"{{0:F{precision}}},{{1:F{precision}}} ", X, Y); + } +#endif + + public static void LogCPath(T[] cpaths, ref int idx, string spaceIndent) + { + int vertexCnt = Convert.ToInt32(cpaths[idx]); + idx += 2; + for (int i = 0; i < vertexCnt; i++) +#if USINGZ + Console.Write(spaceIndent + VertexAsString(cpaths[idx++], cpaths[idx++], cpaths[idx++], 2)); +#else + Console.Write(spaceIndent + VertexAsString(cpaths[idx++], cpaths[idx++], 2)); +#endif + Console.Write("\n"); + } + + public static void LogCPaths(T[]? cpaths, string spaceIndent) + { + if (cpaths == null) return; + int pathCnt = Convert.ToInt32(cpaths[1]); + int idx = 2; + for (int i = 0; i < pathCnt; i++) + LogCPath(cpaths, ref idx, spaceIndent); + } + + // Note: The CPolyTree structure defined in clipper.export.h is + // a simple array of T that contains any number of nested path contours. + + private static void CPolypathCounter(T[] cpolypath, + ref long idx, ref int pathCount, ref long arrayLen) + { + // returns the vertex count of each contained polygon + int vertexCount = Convert.ToInt32(cpolypath[idx++]); + int childCount = Convert.ToInt32(cpolypath[idx++]); + arrayLen += vertexCount * VERTEX_FIELD_CNT + 2; + pathCount++; + idx += vertexCount * VERTEX_FIELD_CNT; + for (int i = 0; i < childCount; i++) + CPolypathCounter(cpolypath, ref idx, ref pathCount, ref arrayLen); + } + private static void ConvertCPolypathToCPaths(T[] cpolypath, ref long idx, + T[] result, ref int resultIdx) + { + T vertexCount = cpolypath[idx++]!; + int vertCnt = (int)Convert.ChangeType(vertexCount, typeof(int)); + int childCount = Convert.ToInt32(cpolypath[idx++]); + result[resultIdx++] = vertexCount; + result[resultIdx++] = (T)Convert.ChangeType(0, typeof(T)); + // parse path + for (int i = 0; i < vertCnt; i++) + { + result[resultIdx++] = cpolypath[idx++]; // x + result[resultIdx++] = cpolypath[idx++]; // y +#if USINGZ + result[resultIdx++] = cpolypath[idx++]; // z +#endif + } + // parse children + for (int i = 0; i < childCount; i++) + ConvertCPolypathToCPaths(cpolypath, ref idx, result, ref resultIdx); + } + + public static void ConvertCPolytreeToCPaths(T[] cpolytree, out T[] result) + { + if (cpolytree == null) + { + result = new T[0]; + return; + } + int topPathsCount = Convert.ToInt32(cpolytree[1]); + long idx = 2; + int pathCount = 0; + long arrayLen = 2; + for (int i = 0; i < topPathsCount; i++) + CPolypathCounter(cpolytree, ref idx, ref pathCount, ref arrayLen); + result = new T[arrayLen]; + result[0] = (T)Convert.ChangeType(arrayLen, typeof(T)); + result[1] = (T)Convert.ChangeType(pathCount, typeof(T)); + int resultIdx = 2; + idx = 2; + for (int i = 0; i < topPathsCount; i++) + ConvertCPolypathToCPaths(cpolytree, ref idx, result, ref resultIdx); + } + + public static void LogPolyPath(T[] polypath, + ref int idx, bool isHole, string spaceIndent, int precision) + { + int polyCnt = Convert.ToInt32(polypath[idx++]); + int childCnt = Convert.ToInt32(polypath[idx++]); + string preamble = isHole ? "Hole: " : (spaceIndent == "") ? + "Polygon: " : "Nested Polygon: "; + Console.Write(spaceIndent + preamble); + spaceIndent += " "; + for (int i = 0; i < polyCnt; i++) +#if USINGZ + Console.Write(VertexAsString(polypath[idx++], polypath[idx++], polypath[idx++], precision)); +#else + Console.Write(VertexAsString(polypath[idx++], polypath[idx++], precision)); +#endif + Console.Write("\n"); + for (int i = 0; i < childCnt; i++) + LogPolyPath(polypath, ref idx, !isHole, spaceIndent, precision); + } + + public static void LogPolytree(T[] polytree, int precision) + { + int cnt = Convert.ToInt32(polytree[1]); + int idx = 2; + for (int i = 0; i < cnt; i++) + LogPolyPath(polytree, ref idx, false, " ", precision); + } + + public static T[]? GetArrayFromIntPtr(IntPtr paths) + { + if (paths == IntPtr.Zero) return null; + if (typeof(T) == typeof(long)) + { + long[] len = new long[1]; + Marshal.Copy(paths, len, 0, 1); + long[] res = new long[(int)len[0]]; + Marshal.Copy(paths, res, 0, (int)len[0]); + return res as T[]; + } + else if (typeof(T) == typeof(double)) + { + double[] len = new double[1]; + Marshal.Copy(paths, len, 0, 1); + double[] res = new double[(int)len[0]]; + Marshal.Copy(paths, res, 0, (int)len[0]); + return res as T[]; + } + else return null; + } + + public static void ConvertArrayOfLongs(long[] longs, out double[] result) + { + result = new double[longs.Length]; + for (int i = 0; i < longs.Length; i++) + result[i] = longs[i]; + } + + public static double StaticCastLongToDouble(long val) + { + byte[] b = BitConverter.GetBytes(val); + return BitConverter.ToDouble(b, 0); + } + public static long StaticCastDoubleToLong(double val) + { + byte[] b = BitConverter.GetBytes(val); + return BitConverter.ToInt64(b, 0); + } + + } +} diff --git a/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp2/Clipper2DllSvg.cs b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp2/Clipper2DllSvg.cs new file mode 100644 index 00000000..84bb74b6 --- /dev/null +++ b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp2/Clipper2DllSvg.cs @@ -0,0 +1,469 @@ +using System.Diagnostics; +using System.Globalization; +using static Clipper2Dll.Clipper2DllCore; + +using RectD = Clipper2Dll.Clipper2DllCore.Rect; + +namespace Clipper2Dll +{ + public class SvgWriter + { + private static RectD rectMax = InvalidRectD; + + public static RectD RectEmpty = new RectD(0, 0, 0, 0); + internal static bool IsValidRect(RectD rec) + { + return rec.right >= rec.left && rec.bottom >= rec.top; + } + + private readonly struct CoordStyle + { + public readonly string FontName; + public readonly int FontSize; + public readonly uint FontColor; + public CoordStyle(string fontname, int fontsize, uint fontcolor) + { + FontName = fontname; + FontSize = fontsize; + FontColor = fontcolor; + } + } + private readonly struct TextInfo + { + public readonly string text; + public readonly int fontSize; + public readonly uint fontColor; + public readonly double posX; + public readonly double posY; + public TextInfo(string text, double x, double y, + int fontsize = 12, uint fontcolor = 0xFF000000) + { + this.text = text; + posX = x; + posY = y; + fontSize = fontsize; + fontColor = fontcolor; + } + } + + private readonly struct CircleInfo + { + public readonly double centerX; + public readonly double centerY; + public readonly double radius; + public readonly uint brushClr; + public readonly uint penClr; + public readonly uint penWidth; + public CircleInfo(double centerX, double centerY, + double radius, uint brushClr, uint penClr, uint penWidth) + { + this.centerX = centerX; + this.centerY = centerY; + this.radius = radius; + this.brushClr = brushClr; + this.penClr = penClr; + this.penWidth = penWidth; + } + } + + private readonly struct PolyInfo + { + public readonly double[] paths; + public readonly uint BrushClr; + public readonly uint PenClr; + public readonly double PenWidth; + public readonly bool ShowCoords; + public readonly bool IsOpen; + public PolyInfo(double[] paths, uint brushcolor, uint pencolor, + double penwidth, bool showcoords = false, bool isopen = false) + { + this.paths = new double[paths.Length]; + paths.CopyTo(this.paths, 0); + BrushClr = brushcolor; + PenClr = pencolor; + PenWidth = penwidth; + ShowCoords = showcoords; + IsOpen = isopen; + } + } + + public FillRule FillRule { get; set; } + private readonly List PolyInfoList = new List(); + private readonly List textInfos = new List(); + private readonly List circleInfos = new List(); + private readonly CoordStyle coordStyle; + + private const string svg_header = "\n" + + "\n\n"; + private const string svg_path_format = "\"\n style=\"fill:{0};" + + " fill-opacity:{1:f2}; fill-rule:{2}; stroke:{3};" + + " stroke-opacity:{4:f2}; stroke-width:{5:f2};\"/>\n\n"; + private const string svg_path_format2 = "\"\n style=\"fill:none; stroke:{0};" + + "stroke-opacity:{1:f2}; stroke-width:{2:f2};\"/>\n\n"; + + public SvgWriter(FillRule fillrule = FillRule.EvenOdd, + string coordFontName = "Verdana", int coordFontsize = 9, uint coordFontColor = 0xFF000000) + { + coordStyle = new CoordStyle(coordFontName, coordFontsize, coordFontColor); + FillRule = fillrule; + } + + public void ClearPaths() + { + PolyInfoList.Clear(); + } + + public void ClearText() + { + textInfos.Clear(); + } + + public void ClearCircles() + { + circleInfos.Clear(); + } + + public void ClearAll() + { + PolyInfoList.Clear(); + textInfos.Clear(); + circleInfos.Clear(); + } + public void AddClosedPath(long[] path, uint brushColor, + uint penColor, double penWidth, bool showCoords = false) + { + long[] tmp = new long[path.Count() +2]; + path.CopyTo(tmp, 2); + tmp[0] = tmp.Length; + tmp[1] = 1; + AddClosedPaths(tmp, brushColor, penColor, penWidth, showCoords); + } + + public void AddClosedPath(double[] path, uint brushColor, + uint penColor, double penWidth, bool showCoords = false) + { + double[] tmp = new double[path.Length + 2]; + path.CopyTo(tmp, 2); + tmp[0] = tmp.Length; + tmp[1] = 1; + AddClosedPaths(tmp, brushColor, penColor, penWidth, showCoords); + } + + public void AddClosedPaths(long[] paths, uint brushColor, + uint penColor, double penWidth, bool showCoords = false) + { + if (paths.Length == 0) return; + double[] pathsD; + ConvertArrayOfLongs(paths, out pathsD); + PolyInfoList.Add(new PolyInfo(pathsD, brushColor, penColor, penWidth, showCoords, false)); + } + + public void AddClosedPaths(double[] paths, uint brushColor, + uint penColor, double penWidth, bool showCoords = false) + { + if (paths.Length == 0) return; + PolyInfoList.Add(new PolyInfo(paths, brushColor, penColor, penWidth, showCoords, false)); + } + + public void AddOpenPath(long[] path, uint penColor, + double penWidth, bool showCoords = false) + { + long[] tmp = new long[path.Length + 2]; + path.CopyTo(tmp, 2); + tmp[0] = tmp.Length; + tmp[1] = 1; + AddOpenPaths(tmp, penColor, penWidth, showCoords); + } + + public void AddOpenPath(double[] path, uint penColor, + double penWidth, bool showCoords = false) + { + double[] tmp = new double[path.Length + 2]; + path.CopyTo(tmp, 2); + tmp[0] = tmp.Length; + tmp[1] = 1; + AddOpenPaths(tmp, penColor, penWidth, showCoords); + } + + public void AddOpenPaths(long[] paths, + uint penColor, double penWidth, bool showCoords = false) + { + if (paths.Length == 0) return; + double[] pathsD; + ConvertArrayOfLongs(paths, out pathsD); + PolyInfoList.Add(new PolyInfo(pathsD, 0x0, penColor, penWidth, showCoords, true)); + } + + public void AddOpenPaths(double[] paths, uint penColor, + double penWidth, bool showCoords = false) + { + if (paths.Length == 0) return; + PolyInfoList.Add(new PolyInfo(paths, + 0x0, penColor, penWidth, showCoords, true)); + } + + + public void AddText(string cap, double posX, double posY, int fontSize, uint fontClr = 0xFF000000) + { + textInfos.Add(new TextInfo(cap, posX, posY, fontSize, fontClr)); + } + + public void AddCircle(double centerX, double centerY, + double radius, uint brushClr, uint penClr, uint penWidth) + { + circleInfos.Add(new CircleInfo(centerX, centerY, radius, brushClr, penClr, penWidth)); + } + + private RectD GetBounds() + { + RectD bounds = new RectD(double.MaxValue, double.MaxValue, -double.MaxValue, -double.MaxValue); + foreach (PolyInfo pi in PolyInfoList) + { + int idx = 2; + long pathsCnt = Convert.ToInt64(pi.paths[1]); + for (int i = 0; i < pathsCnt; i++) + { + long pathLen = Convert.ToInt64(pi.paths[idx]); + idx += 2; + for (int j = 0; j < pathLen; j++) + { + double x = pi.paths[idx++]; + double y = pi.paths[idx++]; +#if USINGZ + idx++; +#endif + if (x < bounds.left) bounds.left = x; + if (x > bounds.right) bounds.right = x; + if (y < bounds.top) bounds.top = y; + if (y > bounds.bottom) bounds.bottom = y; + } + + } + } + if (!IsValidRect(bounds)) return RectEmpty; + return bounds; + } + + private static string ColorToHtml(uint clr) + { + return '#' + (clr & 0xFFFFFF).ToString("X6"); + } + + private static float GetAlpha(uint clr) + { + return ((float)(clr >> 24) / 255); + } + + public bool SaveToFile(string filename, int maxWidth = 0, int maxHeight = 0, int margin = -1) + { + if (margin < 0) margin = 20; + RectD bounds = GetBounds(); + if (bounds.IsEmpty()) return false; + + double scale = 1.0; + if (maxWidth > 0 && maxHeight > 0) + scale = Math.Min( + (maxWidth - margin * 2) / bounds.Width(), + (maxHeight - margin * 2) / bounds.Height()); + + long offsetX = margin - (long)(bounds.left * scale); + long offsetY = margin - (long)(bounds.top * scale); + + StreamWriter writer; + try + { + writer = new StreamWriter(filename); + } + catch + { + return false; + } + + if (maxWidth <= 0 || maxHeight <= 0) + writer.Write(svg_header, (bounds.right - bounds.left) + margin * 2, + (bounds.bottom - bounds.top) + margin * 2); + else + writer.Write(svg_header, maxWidth, maxHeight); + + foreach (PolyInfo pi in PolyInfoList) + { + writer.Write(" \n", + coordStyle.FontName, coordStyle.FontSize, ColorToHtml(coordStyle.FontColor)); + + idx = 2; + pathsCnt = Convert.ToInt64(pi.paths[1]); + for (int i = 0; i < pathsCnt; i++) + { + long pathLen = Convert.ToInt64(pi.paths[idx]); + idx += 2; + for (int j = 1; j < pathLen; j++) + { + double x = pi.paths[idx++]; + double y = pi.paths[idx++]; +#if USINGZ + double z = pi.paths[idx++]; + writer.Write("{2:f2},{3:f2},{4}\n", + (x * scale + offsetX), (y * scale + offsetY), x, y, z); +#else + writer.Write("{2:f2},{3:f2}\n", + (x * scale + offsetX), (y * scale + offsetY), x, y); +#endif + } + } + writer.Write("\n\n"); + } + } + + foreach (CircleInfo circInfo in circleInfos) + { + writer.Write("\n \n\n", + circInfo.centerX * scale + offsetX, circInfo.centerY * scale + offsetY, + circInfo.radius * scale, ColorToHtml(circInfo.penClr), circInfo.penWidth, + ColorToHtml(circInfo.brushClr), GetAlpha(circInfo.brushClr)); + } + + + foreach (TextInfo captionInfo in textInfos) + { + writer.Write("\n", + captionInfo.fontSize, ColorToHtml(captionInfo.fontColor)); + writer.Write("{2}\n\n", + captionInfo.posX * scale + offsetX, captionInfo.posY * scale + offsetY, captionInfo.text); + } + + writer.Write("\n"); + writer.Close(); + return true; + } + } //end SvgWriter + +public static class SvgWriterUtils + { + public static void AddCaption(SvgWriter svg, string caption, int x, int y) + { + svg.AddText(caption, x, y, 14); + } + + public static void AddSubject(SvgWriter svg, long[] path) + { + svg.AddClosedPath(path, 0x1800009C, 0xAAB3B3DA, 0.8); + } + public static void AddSubject(SvgWriter svg, double[] path) + { + svg.AddClosedPath(path, 0x1800009C, 0xAAB3B3DA, 0.8); + } + + public static void AddSubjects(SvgWriter svg, long[] paths) + { + svg.AddClosedPaths(paths, 0x1800009C, 0xAAB3B3DA, 0.8); + } + public static void AddOpenSubjects(SvgWriter svg, long[] paths) + { + svg.AddOpenPaths(paths, 0xAAB3B3DA, 0.8); + } + + public static void AddSubjects(SvgWriter svg, double[] paths) + { + svg.AddClosedPaths(paths, 0x1800009C, 0xAAB3B3DA, 0.8); + } + + public static void AddOpenSubjects(SvgWriter svg, double[] paths) + { + svg.AddOpenPaths(paths, 0xAAB3B3DA, 1.2); + } + + public static void AddClip(SvgWriter svg, long[] path) + { + svg.AddClosedPath(path, 0x129C0000, 0xCCFFA07A, 0.8); + } + + public static void AddClip(SvgWriter svg, double[] path) + { + svg.AddClosedPath(path, 0x129C0000, 0xCCFFA07A, 0.8); + } + + public static void AddClips(SvgWriter svg, long[] paths) + { + svg.AddClosedPaths(paths, 0x129C0000, 0xCCFFA07A, 0.8); + } + + public static void AddClips(SvgWriter svg, double[] paths) + { + svg.AddClosedPaths(paths, 0x129C0000, 0xCCFFA07A, 0.8); + } + + public static void AddSolution(SvgWriter svg, long[] paths, + bool show_coords, bool is_closed = true, bool is_joined = true) + { + svg.AddClosedPaths(paths, 0x4080ff9C, 0xFF003300, 1.5, show_coords); + } + + public static void AddOpenSolution(SvgWriter svg, long[] paths, bool show_coords) + { + svg.AddOpenPaths(paths, 0xFF003300, 2.2, show_coords); + } + + public static void AddSolution(SvgWriter svg, double[] paths, bool show_coords) + { + svg.AddClosedPaths(paths, 0x4080ff9C, 0xFF003300, 1.5, show_coords); + } + + public static void AddOpenSolution(SvgWriter svg, double[] paths, bool show_coords) + { + svg.AddOpenPaths(paths, 0xFF003300, 2.2, show_coords); + } + + public static void OpenFileWithDefaultApp(string filename) + { + string path = Path.GetFullPath(filename); + if (!File.Exists(path)) return; + Process p = new Process() { StartInfo = new ProcessStartInfo(path) { UseShellExecute = true } }; + p.Start(); + } + + } //end SvgWriterUtils +} diff --git a/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp2/Program.cs b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp2/Program.cs new file mode 100644 index 00000000..753f1b92 --- /dev/null +++ b/DLL/TEST_APPS/CSharp_TestApps/CSharp_TestApp2/Program.cs @@ -0,0 +1,123 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 14 August 2024 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2024 * +* License : https://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +using System.Diagnostics; +using static Clipper2Dll.Clipper2DllCore; +using static Clipper2Dll.SvgWriterUtils; +using Clipper2Lib; + +namespace ClipperDllDemo +{ + public class Application + { + private static void MakeRandomCPaths(int width, int height, int count, + Random rand, out T[] result) + { + T[] coords = new T[count * VERTEX_FIELD_CNT]; + int idx = 0; + for (int i = 0; i < count; i++) + { + coords[idx++] = (T)Convert.ChangeType(rand.Next(width), typeof(T)); + coords[idx++] = (T)Convert.ChangeType(rand.Next(height), typeof(T)); +#if USINGZ + coords[idx++] = (T)Convert.ChangeType(0, typeof(T)); +#endif + } + + long arrayLen = count * VERTEX_FIELD_CNT + 4; + result = new T[arrayLen]; + result[0] = (T)Convert.ChangeType(arrayLen, typeof(T)); + result[1] = (T)Convert.ChangeType(1, typeof(T)); + result[2] = (T)Convert.ChangeType(count, typeof(T)); + result[3] = (T)Convert.ChangeType(0, typeof(T)); + coords.CopyTo(result, 4); + } + + private static void ConvertCPathsToPaths64(long[] cpaths, out Paths64 result) + { + if (cpaths[1] != 1) + throw new Exception("This function assumes cpaths contains only a single path"); + result = new Paths64(); + long pathLen = cpaths[2]; + Path64 path = new Path64((int)pathLen); + int idx = 4; + for (int i = 0; i < pathLen; i++) + { + long x = cpaths[idx++]; + long y = cpaths[idx++]; +#if USINGZ + long z = cpaths[idx++]; + path.Add(new Point64(x, y, z)); +#else + path.Add(new Point64(x, y)); +#endif + } + result.Add(path); + } + + public static void Main() + { + + //string? ver = Marshal.PtrToStringAnsi(Version()); + //Console.WriteLine(ver + "\n"); + + long timeMsec; + Random rand = new(); + const int edgeCount = 2500; + + long[] cSubjects; + MakeRandomCPaths(600, 400, edgeCount, rand, out cSubjects); + long[] cClips; + MakeRandomCPaths(600, 400, edgeCount, rand, out cClips); + + Paths64 solution; + ConvertCPathsToPaths64(cSubjects, out Paths64 subjects); + ConvertCPathsToPaths64(cClips, out Paths64 clips); + + ////////////////////////////////////////////////////////////////////// + // Use Dynamically Linked C++ compiled library (ie use the DLL) + ////////////////////////////////////////////////////////////////////// + Stopwatch sw1 = Stopwatch.StartNew(); + if (BooleanOp64((int)Clipper2Dll.Clipper2DllCore.ClipType.Intersection, + (int)Clipper2Dll.Clipper2DllCore.FillRule.NonZero, + cSubjects, null, cClips, out IntPtr cSol, out IntPtr cSolOpen, false, false) != 0) + return; + + long[]? cSolution = GetArrayFromIntPtr(cSol); + if (cSolution == null) return; + DisposeArray64(ref cSol); + DisposeArray64(ref cSolOpen); + sw1.Stop(); + timeMsec = sw1.ElapsedMilliseconds; + Console.WriteLine($"Time using DLL (C++ code): {timeMsec} ms"); + + string fileName = "../../../clipper2_dll.svg"; + Clipper2Dll.SvgWriter svg = new(Clipper2Dll.Clipper2DllCore.FillRule.NonZero); + AddSubjects(svg, cSubjects); + AddClips(svg, cClips); + AddSolution(svg, cSolution, false); + svg.SaveToFile(fileName, 800, 600, 20); + OpenFileWithDefaultApp(fileName); + + ////////////////////////////////////////////////////////////////////// + // Use Clipper2's statically linked C# compiled library + ////////////////////////////////////////////////////////////////////// + + Stopwatch sw2 = Stopwatch.StartNew(); + solution = Clipper.Intersect(subjects, clips, Clipper2Lib.FillRule.NonZero); + sw2.Stop(); + timeMsec = sw2.ElapsedMilliseconds; + Console.WriteLine($"Time using C# code : {timeMsec} ms"); + ////////////////////////////////////////////////////////////////////// + + //Console.WriteLine("Press any key to exit ... "); + //Console.ReadKey(); + } + + } //end Application +} //namespace diff --git a/DLL/CSharp_TestApp2/COMPILE AND ADD Clipper2_64.dll HERE b/DLL/TEST_APPS/Delphi_TestApps/Output/COPY Clipper2 DLLs HERE similarity index 100% rename from DLL/CSharp_TestApp2/COMPILE AND ADD Clipper2_64.dll HERE rename to DLL/TEST_APPS/Delphi_TestApps/Output/COPY Clipper2 DLLs HERE diff --git a/DLL/TEST_APPS/Delphi_TestApps/TestApp1/Clipper2DllCore.pas b/DLL/TEST_APPS/Delphi_TestApps/TestApp1/Clipper2DllCore.pas new file mode 100644 index 00000000..e5d372e7 --- /dev/null +++ b/DLL/TEST_APPS/Delphi_TestApps/TestApp1/Clipper2DllCore.pas @@ -0,0 +1,329 @@ +unit Clipper2DllCore; + +(******************************************************************************* +* Author : Angus Johnson * +* Date : 12 August 2024 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2024 * +* License : https://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************) + +interface + +uses + SysUtils, Classes, Math; + +type +{$IFDEF USINGZ} + Ztype = type Int64; //double; // + PZtype = ^Ztype; +{$ENDIF} + + PPoint64 = ^TPoint64; + TPoint64 = record + X, Y: Int64; +{$IFDEF USINGZ} + Z: Ztype; +{$ENDIF} + end; + + PPointD = ^TPointD; + TPointD = record + X, Y: double; +{$IFDEF USINGZ} + Z: Ztype; +{$ENDIF} + end; + + // The most commonly used filling rules for polygons are EvenOdd and NonZero. + // https://en.wikipedia.org/wiki/Even-odd_rule + // https://en.wikipedia.org/wiki/Nonzero-rule + TFillRule = (frEvenOdd, frNonZero, frPositive, frNegative); + TClipType = (ctNone, ctIntersection, ctUnion, ctDifference, ctXor); + + TArrayOfBoolean = array of Boolean; + TArrayOfInt64 = array of Int64; + TArrayOfDouble = array of double; + + TRect64 = record + public + Left : Int64; + Top : Int64; + Right : Int64; + Bottom : Int64; + end; + + TRectD = record + public + Left : double; + Top : double; + Right : double; + Bottom : double; + end; + + CInt64arr = array[0..$FFFF] of Int64; + PCInt64arr = ^CInt64arr; + CPath64 = PCInt64arr; + CPaths64 = PCInt64arr; + CPolyPath64 = PCInt64arr; + CPolytree64 = PCInt64arr; + + CDblarr = array[0..$FFFF] of Double; + PCDblarr = ^CDblarr; + CPathD = PCDblarr; + CPathsD = PCDblarr; + CPolyPathD = PCDblarr; + CPolytreeD = PCDblarr; + +{$IFDEF USINGZ} +type + TZCallback64_DLL = procedure (const bot1, top1, bot2, top2: TPoint64; + var intersectPt: TPoint64); + TZCallbackD_DLL = procedure (const bot1, top1, bot2, top2: TPointD; + var intersectPt: TPointD); +{$ENDIF} + +const +{$IFDEF WIN64} + {$IFDEF USINGZ} + CLIPPER2_DLL = 'Clipper2_Z_64.dll'; + {$ELSE} + CLIPPER2_DLL = 'Clipper2_64.dll'; + {$ENDIF} +{$ELSE} + {$IFDEF USINGZ} + CLIPPER2_DLL = 'Clipper2_Z_32.dll'; + {$ELSE} + CLIPPER2_DLL = 'Clipper2_32.dll'; + {$ENDIF} +{$ENDIF} + +{$IFDEF USINGZ} + VERTEX_FIELD_CNT = 3; +{$ELSE} + VERTEX_FIELD_CNT = 2; +{$ENDIF} + + Intersection = 1; Union = 2; Difference =3; Xor_ = 4; + EvenOdd = 0; NonZero = 1; Positive = 2; Negative = 3; + POINT_FIELD_CNT = {$IFDEF USINGZ} 3 {$ELSE} 2 {$ENDIF}; + +//////////////////////////////////////////////////////// +// Clipper2 DLL functions +//////////////////////////////////////////////////////// + +function Version(): PAnsiChar; cdecl; + external CLIPPER2_DLL name 'Version'; + +procedure DisposeExportedArray64(var cps: PCInt64arr); cdecl; + external CLIPPER2_DLL name 'DisposeArray64'; +procedure DisposeExportedArrayD(var cp: PCDblarr); cdecl; + external CLIPPER2_DLL name 'DisposeArrayD'; + +function BooleanOp64(cliptype: UInt8; fillrule: UInt8; + const subjects: CPaths64; const subjects_open: CPaths64; + const clips: CPaths64; out solution: CPaths64; + out solution_open: CPaths64; + preserve_collinear: boolean = true; + reverse_solution: boolean = false): integer; cdecl; + external CLIPPER2_DLL name 'BooleanOp64'; +function BooleanOp_PolyTree64(cliptype: UInt8; fillrule: UInt8; + const subjects: CPaths64; const subjects_open: CPaths64; + const clips: CPaths64; out solution: CPolyTree64; + out solution_open: CPaths64; + preserve_collinear: boolean = true; + reverse_solution: boolean = false): integer; cdecl; + external CLIPPER2_DLL name 'BooleanOp_PolyTree64'; + +function BooleanOpD(cliptype: UInt8; fillrule: UInt8; + const subjects: CPathsD; const subjects_open: CPathsD; + const clips: CPathsD; out solution: CPathsD; out solution_open: CPathsD; + precision: integer = 2; + preserve_collinear: boolean = true; + reverse_solution: boolean = false): integer; cdecl; + external CLIPPER2_DLL name 'BooleanOpD'; +function BooleanOp_PolyTreeD(cliptype: UInt8; fillrule: UInt8; + const subjects: CPathsD; const subjects_open: CPathsD; + const clips: CPathsD; out solution: CPolyTreeD; out solution_open: CPathsD; + precision: integer = 2; + preserve_collinear: boolean = true; + reverse_solution: boolean = false): integer; cdecl; + external CLIPPER2_DLL name 'BooleanOp_PolyTreeD'; +function InflatePaths64(const paths: CPaths64; + delta: double; jointype, endtype: UInt8; miter_limit: double = 2.0; + arc_tolerance: double = 0.0; + reverse_solution: Boolean = false): CPaths64; cdecl; + external CLIPPER2_DLL name 'InflatePaths64'; +function InflatePathsD(const paths: CPathsD; + delta: double; jointype, endtype: UInt8; precision: integer = 2; + miter_limit: double = 2.0; arc_tolerance: double = 0.0; + reverse_solution: Boolean = false): CPathsD; cdecl; + external CLIPPER2_DLL name 'InflatePathsD'; + +function RectClip64(const rect: TRect64; const paths: CPaths64; + convexOnly: Boolean = false): CPaths64; cdecl; + external CLIPPER2_DLL name 'RectClip64'; +function RectClipD(const rect: TRectD; const paths: CPathsD; + precision: integer = 2; convexOnly: Boolean = false): CPathsD; cdecl; + external CLIPPER2_DLL name 'RectClipD'; +function RectClipLines64(const rect: TRect64; + const paths: CPaths64): CPaths64; cdecl; + external CLIPPER2_DLL name 'RectClipLines64'; +function RectClipLinesD(const rect: TRectD; + const paths: CPathsD; precision: integer = 2): CPathsD; cdecl; + external CLIPPER2_DLL name 'RectClipLinesD'; + +{$IFDEF USINGZ} +procedure SetZCallback64(zCallback64: TZCallback64_DLL); cdecl; + external CLIPPER2_DLL name 'SetZCallback64'; +procedure SetZCallbackD(zCallbackD: TZCallbackD_DLL); cdecl; + external CLIPPER2_DLL name 'SetZCallbackD'; +{$ENDIF} + +function Rect64(const left, top, right, bottom: Int64): TRect64; +function RectD(const left, top, right, bottom: double): TRectD; +function IsValidRect64(const rec: TRect64): Boolean; +function IsValidRectD(const rec: TRectD): Boolean; + +function GetBounds(paths: CPathsD): TRectD; +function MakeCPaths64(const coords: array of Int64): CPaths64; +function MakeCPathsD(const coords: array of double): CPathsD; + +{$IFDEF USINGZ} +function PointD(const X, Y: Double; Z: ZType = 0): TPointD; +{$ELSE} +function PointD(const X, Y: Double): TPointD; +{$ENDIF} + +procedure DisposeLocalArray64(cp: PCInt64arr); +procedure DisposeLocalArrayD(cp: PCDblarr); + +implementation + +{$IFDEF USINGZ} +function PointD(const X, Y: Double; Z: ZType = 0): TPointD; +begin + Result.X := X; + Result.Y := Y; + Result.Z := Z; +end; +{$ELSE} +function PointD(const X, Y: Double): TPointD; +begin + Result.X := X; + Result.Y := Y; +end; +{$ENDIF} +//------------------------------------------------------------------------------ + +function Rect64(const left, top, right, bottom: Int64): TRect64; +begin + Result.Left := left; + Result.Top := top; + Result.Right := right; + Result.Bottom := bottom; +end; +//------------------------------------------------------------------------------ + +function RectD(const left, top, right, bottom: double): TRectD; +begin + Result.Left := left; + Result.Top := top; + Result.Right := right; + Result.Bottom := bottom; +end; +//------------------------------------------------------------------------------ + +function IsValidRect64(const rec: TRect64): Boolean; +begin + Result := (rec.Left <= rec.Right) and (rec.Top < rec.Bottom); +end; +//------------------------------------------------------------------------------ + +function IsValidRectD(const rec: TRectD): Boolean; +begin + Result := (rec.Left <= rec.Right) and (rec.Top < rec.Bottom); +end; +//------------------------------------------------------------------------------ + +function GetBounds(paths: CPathsD): TRectD; +var + i,j, idx : Integer; + pathsCnt, pathLen : Integer; + x,y: double; +begin + Result := RectD(MaxDouble, MaxDouble, -MaxDouble, -MaxDouble); + pathsCnt := Round(paths[1]); + idx := 2; + for i := 0 to pathsCnt -1 do + begin + pathLen := Round(paths[idx]); + inc(idx, 2); + for j := 0 to pathLen -1 do + begin + x := paths[idx]; inc(idx); + y := paths[idx]; inc(idx); +{$IFDEF USINGZ} + inc(idx); +{$ENDIF} + if (x < Result.left) then Result.left := x; + if (x > Result.right) then Result.right := x; + if (y < Result.top) then Result.top := y; + if (y > Result.bottom) then Result.bottom := y; + end; + end; + if not IsValidRectD(Result) then + Result := RectD(0,0,0,0); +end; +//------------------------------------------------------------------------------ + +function MakeCPaths64(const coords: array of Int64): CPaths64; +var + i, arrayLen, pathLen: integer; +begin + pathLen := length(coords) div VERTEX_FIELD_CNT; + arrayLen := length(coords) + 4; + GetMem(Result, arrayLen * sizeOf(Int64)); + Result[0] := arrayLen; + Result[1] := 1; + Result[2] := pathLen; + Result[3] := 0; + if pathLen > 0 then + Move(coords[0], Result[4], length(coords) * SizeOf(Int64)); +end; +//------------------------------------------------------------------------------ + +function MakeCPathsD(const coords: array of double): CPathsD; +var + i, arrayLen, pathLen: integer; +begin + pathLen := length(coords) div VERTEX_FIELD_CNT; + arrayLen := length(coords) + 4; + GetMem(Result, arrayLen * SizeOf(double)); + Result[0] := arrayLen; + Result[1] := 1; + Result[2] := pathLen; + Result[3] := 0; + if pathLen > 0 then + Move(coords[0], Result[4], length(coords) * SizeOf(double)); +end; +//------------------------------------------------------------------------------ + +//////////////////////////////////////////////////////// +// functions related to Clipper2 DLL structures +// including path format conversion functions +//////////////////////////////////////////////////////// + +procedure DisposeLocalArray64(cp: PCInt64arr); +begin + FreeMem(cp); +end; +//----------------------------------------------------- + +procedure DisposeLocalArrayD(cp: PCDblarr); +begin + FreeMem(cp); +end; +//----------------------------------------------------- + +end. diff --git a/DLL/TEST_APPS/Delphi_TestApps/TestApp1/Clipper2DllSVG.pas b/DLL/TEST_APPS/Delphi_TestApps/TestApp1/Clipper2DllSVG.pas new file mode 100644 index 00000000..7559fc26 --- /dev/null +++ b/DLL/TEST_APPS/Delphi_TestApps/TestApp1/Clipper2DllSVG.pas @@ -0,0 +1,774 @@ + unit Clipper2DllSVG; + +(******************************************************************************* +* Author : Angus Johnson * +* Date : 12 August 2024 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2024 * +* License : https://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************) + +interface + +uses + Windows, ShellApi, + Classes, SysUtils, Math, Clipper2DllCore; + +const + black = $FF000000; + white = $FFFFFFFF; + maroon = $FF800000; + navy = $FF000080; + blue = $FF0000FF; + red = $FFFF0000; + green = $FF008000; + yellow = $FFFFFF00; + lime = $FF00FF00; + fuscia = $FFFF00FF; + aqua = $FF00FFFF; + + displayWidth = 400; + displayHeight = 400; + +type + +{$IFDEF RECORD_METHODS} + TCoordStyle = record +{$ELSE} + TCoordStyle = object +{$ENDIF} + FontName: string; + FontSize: integer; + FontColor: cardinal; + constructor Create(const afontname: string; + afontsize: integer; afontcolor: Cardinal); + end; + + TArrayOfString = array of string; + TArrayOfInteger = array of Integer; + + PTextInfo = ^TTextInfo; +{$IFDEF RECORD_METHODS} + TTextInfo = record +{$ELSE} + TTextInfo = object +{$ENDIF} + x,y : double; + text: string; + fontSize: integer; + fontColor: Cardinal; + Bold: Boolean; + constructor Create(atext: string; _x, _y: double; + afontsize: integer = 12; + afontcolor: Cardinal = black; + aBold: Boolean = false); + end; + + PPolyInfo = ^TPolyInfo; + TPolyInfo = record + paths : CPathsD; + BrushClr : Cardinal; + PenClr : Cardinal; + PenWidth : double; + ShowCoords: Boolean; + ShowFrac : Boolean; + IsOpen : Boolean; + dashes : TArrayOfInteger; + end; + + PCircleInfo = ^TCircleInfo; + TCircleInfo = record + center : TPointD; + radius : double; + BrushClr : Cardinal; + PenClr : Cardinal; + PenWidth : double; + end; + + TSvgWriter = class + private + fFillRule : TFillRule; + fCoordStyle : TCoordStyle; + fPolyInfos : TList; + fCircleInfos: TList; + fTextInfos : TList; + function GetBounds: TRectD; + public + constructor Create(fillRule: TFillRule; + const coordFontName: string = 'Verdana'; + coordFontSize: integer = 9; + coordFontColor: Cardinal = black); + destructor Destroy; override; + + procedure AddPaths(const paths: CPaths64; isOpen: Boolean; + brushColor, penColor: Cardinal; + penWidth: double; showCoords: Boolean = false); overload; + + procedure AddPaths(const paths: CPathsD; isOpen: Boolean; + brushColor, penColor: Cardinal; + penWidth: double; showCoords: Boolean = false); overload; + + procedure AddCircle(const center: TPoint64; radius: double; + brushColor, penColor: Cardinal; penWidth: double); overload; + procedure AddCircle(const center: TPointD; radius: double; + brushColor, penColor: Cardinal; penWidth: double); overload; + procedure AddText(text: string; x,y: double; + fontSize: integer = 14; fontClr: Cardinal = black; bold: Boolean = false); + + function SaveToFile(const filename: string; + maxWidth: integer = 0; maxHeight: integer = 0; + margin: integer = 20): Boolean; + procedure ClearPaths; + procedure ClearText; + procedure ClearAll; + end; + + procedure AddSubjects(svg: TSvgWriter; const paths: CPaths64; showCoords: Boolean = false); overload; + procedure AddOpenSubjects(svg: TSvgWriter; const paths: CPaths64); overload; + procedure AddClips(svg: TSvgWriter; const paths: CPaths64; showCoords: Boolean = false); overload; + procedure AddSolution(svg: TSvgWriter; const paths: CPaths64; showCoords: Boolean = false); overload; + procedure AddOpenSolution(svg: TSvgWriter; const paths: CPaths64); overload; + + procedure SaveSvg(svg: TSvgWriter; const filename: string; + width: integer = 0; height: integer = 0; margin: integer = 0); overload; + + procedure AddSubjects(svg: TSvgWriter; const paths: CPathsD; showCoords: Boolean = false); overload; + procedure AddOpenSubjects(svg: TSvgWriter; const paths: CPathsD); overload; + procedure AddClips(svg: TSvgWriter; const paths: CPathsD; showCoords: Boolean = false); overload; + procedure AddSolution(svg: TSvgWriter; const paths: CPathsD; showCoords: Boolean= false); overload; + procedure AddOpenSolution(svg: TSvgWriter; const paths: CPathsD); overload; + + procedure DisplaySVG(const sub, subo, clp, sol, solo: CPaths64; + const svgName: string; width: integer = displayWidth; + height: integer = displayHeight; showCoords: Boolean = false); overload; + procedure DisplaySVG(const sub, subo, clp, sol, solo: CPathsD; + const svgName: string; width: integer = displayWidth; + height: integer = displayHeight; showCoords: Boolean = false); overload; + + +implementation + +const + MaxRect: TRectD = (left: MaxDouble; + Top: MaxDouble; Right: -MaxDouble; Bottom: -MaxDouble); + + svg_header: string = + ''; + svg_path_format: string = '"'+#10+' style="fill:%s;' + + ' fill-opacity:%1.2f; fill-rule:%s; stroke:%s;' + + ' stroke-opacity:%1.2f; stroke-width:%1.2f;"/>'#10; + svg_path_format2: string = '"'+#10+ + ' style="fill:none; stroke:%s; ' + + 'stroke-opacity:%1.2f; stroke-width:%1.2f; %s"/>'#10; + +function ColorToHtml(color: Cardinal): string; +begin + Result := Format('#%6.6x', [color and $FFFFFF]);; +end; +//------------------------------------------------------------------------------ + +function GetAlpha(clr: Cardinal): double; +begin + Result := (clr shr 24) / 255; +end; +//------------------------------------------------------------------------------ + +function CopyCPaths64(cpaths: CPaths64): CPaths64; +var + arrayLen, byteSpace: int64; +begin + arrayLen := cpaths[0]; + byteSpace := arrayLen * SizeOf(Int64); + GetMem(Result, byteSpace); + Move(cpaths[0], Result[0], byteSpace); +end; +//------------------------------------------------------------------------------ + +function CopyCPathsD(cpaths: CPathsD): CPathsD; +var + arrayLen, byteSpace: int64; +begin + arrayLen := Round(cpaths[0]); + byteSpace := arrayLen * SizeOf(double); + GetMem(Result, byteSpace); + Move(cpaths[0], Result[0], byteSpace); +end; +//------------------------------------------------------------------------------ + +function CopyConvertCPath64(cpaths: CPaths64): CPathsD; +var + i: integer; + arrayLen, byteSpace: int64; +begin + arrayLen := cpaths[0]; + byteSpace := arrayLen * SizeOf(double); + GetMem(Result, byteSpace); + for i := 0 to arrayLen -1 do Result[i] := cpaths[i]; +end; +//------------------------------------------------------------------------------ + +constructor TCoordStyle.Create(const afontname: string; + afontsize: integer; afontcolor: Cardinal); +begin + Self.FontName := afontname; + Self.FontSize := afontsize; + Self.FontColor := afontcolor; +end; +//------------------------------------------------------------------------------ + +constructor TTextInfo.Create(atext: string; _x, _y: double; + afontsize: integer = 12; afontcolor: Cardinal = black; + aBold: Boolean = false); +begin + self.x := _x; + self.y := _y; + self.text := text; + self.fontSize := afontsize; + self.fontColor := afontcolor; + Self.Bold := aBold; +end; +//------------------------------------------------------------------------------ + +constructor TSvgWriter.Create(fillRule: TFillRule; + const coordFontName: string = 'Verdana'; + coordFontSize: integer = 9; + coordFontColor: Cardinal = black); +begin + fFillRule := fillRule; + fCoordStyle.FontName := coordFontName; + fCoordStyle.FontSize := coordFontSize; + fCoordStyle.FontColor := coordFontColor; + fPolyInfos := TList.Create; + fCircleInfos := TList.Create; + fTextInfos := TList.Create; +end; +//------------------------------------------------------------------------------ + +destructor TSvgWriter.Destroy; +begin + ClearAll; + fPolyInfos.Free; + fCircleInfos.Free; + fTextInfos.Free; + inherited; +end; +//------------------------------------------------------------------------------ + +procedure TSvgWriter.AddPaths(const paths: CPaths64; + isOpen: Boolean; brushColor, penColor: Cardinal; + penWidth: double; showCoords: Boolean = false); +var + pi: PPolyInfo; +begin + if paths = nil then Exit; + new(pi); + pi.paths := CopyConvertCPath64(paths); + pi.BrushClr := brushColor; + pi.PenClr := penColor; + pi.PenWidth := penWidth; + pi.ShowCoords := showCoords; + pi.ShowFrac := false; + pi.IsOpen := isOpen; + fPolyInfos.Add(pi); +end; +//------------------------------------------------------------------------------ + +procedure TSvgWriter.AddPaths(const paths: CPathsD; isOpen: Boolean; + brushColor, penColor: Cardinal; + penWidth: double; showCoords: Boolean = false); +var + pi: PPolyInfo; +begin + if paths = nil then Exit; + new(pi); + pi.paths := CopyCPathsD(paths); + pi.BrushClr := brushColor; + pi.PenClr := penColor; + pi.PenWidth := penWidth; + pi.ShowCoords := showCoords; + pi.ShowFrac := true; + pi.IsOpen := isOpen; + fPolyInfos.Add(pi); +end; +//------------------------------------------------------------------------------ + +procedure TSvgWriter.AddCircle(const center: TPoint64; + radius: double; brushColor, penColor: Cardinal; penWidth: double); +var + ci: PCircleInfo; +begin + new(ci); + ci.center := PointD(center.X, center.Y); + ci.radius := radius; + ci.BrushClr := brushColor; + ci.PenClr := penColor; + ci.PenWidth := penWidth; + fCircleInfos.Add(ci); +end; +//------------------------------------------------------------------------------ + +procedure TSvgWriter.AddCircle(const center: TPointD; + radius: double; brushColor, penColor: Cardinal; penWidth: double); +var + ci: PCircleInfo; +begin + new(ci); + ci.center := center; + ci.radius := radius; + ci.BrushClr := brushColor; + ci.PenClr := penColor; + ci.PenWidth := penWidth; + fCircleInfos.Add(ci); +end; +//------------------------------------------------------------------------------ + +procedure TSvgWriter.AddText(text: string; x,y: double; + fontSize: integer; fontClr: Cardinal; bold: Boolean); +var + ti: PTextInfo; +begin + new(ti); + ti.x := x; + ti.y := y; + ti.text := text; + ti.fontSize := fontSize; + ti.fontColor := fontClr; + ti.Bold := bold; + fTextInfos.Add(ti); +end; +//------------------------------------------------------------------------------ + +function TSvgWriter.GetBounds: TRectD; +var + i: integer; + bounds: TRectD; +begin + Result := MaxRect; + for i := 0 to fPolyInfos.Count -1 do + with PPolyInfo(fPolyInfos[i])^ do + begin + bounds := Clipper2DllCore.GetBounds(paths); + if not IsValidRectD(bounds) then Continue; + if (bounds.left < Result.Left) then Result.Left := bounds.Left; + if (bounds.right> Result.Right) then Result.Right := bounds.Right; + if (bounds.top < Result.Top) then Result.Top := bounds.Top; + if (bounds.bottom > Result.Bottom) then Result.Bottom := bounds.Bottom; + end; +end; +//------------------------------------------------------------------------------ + +procedure TSvgWriter.ClearPaths; +var + i: integer; +begin + for i := 0 to fPolyInfos.Count -1 do + begin + Dispose(PPolyInfo(fPolyInfos[i]).paths); + Dispose(PPolyInfo(fPolyInfos[i])); + end; + fPolyInfos.Clear; + + for i := 0 to fCircleInfos.Count -1 do + Dispose(PCircleInfo(fCircleInfos[i])); + fCircleInfos.Clear; +end; +//------------------------------------------------------------------------------ + +procedure TSvgWriter.ClearText; +var + i: integer; +begin + for i := 0 to fTextInfos.Count -1 do + Dispose(PTextInfo(fTextInfos[i])); + fTextInfos.Clear; +end; +//------------------------------------------------------------------------------ + +procedure TSvgWriter.ClearAll; +begin + ClearText; + ClearPaths; +end; +//------------------------------------------------------------------------------ + +function TSvgWriter.SaveToFile(const filename: string; + maxWidth: integer = 0; maxHeight: integer = 0; margin: integer = 20): Boolean; +var + i, j, k, idx : integer; + frac : integer; + showCo : boolean; + showFr : boolean; + x,y : double; +{$IFDEF USINGZ} + z : double; +{$ENDIF} + bounds : TRectD; + scale : double; + offsetX, offsetY : integer; + s : string; + sInline, dashStr : string; + sl : TStringList; + pathsCnt, pathLen : integer; + formatSettings: TFormatSettings; +const + fillRuleStr: array[boolean] of string = ('evenodd', 'nonzero'); + boldFont: array[boolean] of string = ('normal', '700'); + gap: integer = 5; + + procedure Add(const s: string); + begin + sl.Add( sInline + s); + sInline := ''; + end; + + procedure AddInline(const s: string); + begin + sInline := sInline + s; + end; + +begin + Result := false; + +{$IF NOT Defined(fpc) AND (CompilerVersion > 19)} //Delphi XE + + formatSettings := TFormatSettings.Create; +{$IFEND} + formatSettings.DecimalSeparator := '.'; + + // adjust margin + if (margin < 20) then margin := 20; + showCo := false; + showFr := false; + for i := 0 to fPolyInfos.Count -1 do + with PPolyInfo(fPolyInfos[i])^ do + if ShowCoords then + begin + showCo := true; + if showFrac then showFr := true; + + end; + if showCo then + begin + if showFr then + inc(margin, Abs(fCoordStyle.FontSize *4)) else + inc(margin, Abs(fCoordStyle.FontSize *2)); + end; + + // get scale and offset + bounds := GetBounds; + if (bounds.Left >= bounds.Right) or + (bounds.Top >= bounds.Bottom) then Exit; + + scale := 1.0; + if (maxWidth > 0) and (maxHeight > 0) then + scale := 1.0 / Max((bounds.right - bounds.left) / + (maxWidth - margin * 2), (bounds.bottom - bounds.top) / + (maxHeight - margin * 2)); + + offsetX := margin - Round(bounds.left * scale); + offsetY := margin - Round(bounds.top * scale); + + // write SVG + sl := TStringList.Create; + try + if (maxWidth <= 0) or (maxHeight <= 0) then + Add(Format(svg_header, + [Round(bounds.right - bounds.left) + margin * 2, + Round(bounds.bottom - bounds.top) + margin * 2], formatSettings)) + else + Add(Format(svg_header, [maxWidth, maxHeight], formatSettings)); + + for i := 0 to fPolyInfos.Count -1 do + with PPolyInfo(fPolyInfos[i])^ do + begin + AddInline(' ', + [FontName, FontSize, ColorToHtml(FontColor)], formatSettings)); + + idx := 2; + for j := 0 to pathsCnt -1 do + begin + pathLen := Round(paths[idx]); inc(idx, 2); + for k := 0 to pathLen -1 do + begin + x := paths[idx]; inc(idx); + y := paths[idx]; inc(idx); + {$IFDEF USINGZ} + z := paths[idx]; inc(idx); + Add(Format(' %1.*f,%1.*f,%1.*f', + [x * scale + offsetX, y * scale + offsetY, frac, x, frac, y, frac, z], + formatSettings)); + {$ELSE} + Add(Format(' %1.*f,%1.*f', + [x * scale + offsetX, y * scale + offsetY, frac, x, frac, y], + formatSettings)); + {$ENDIF} + end; + Add(''#10); + end; + end; + + end; + + + for i := 0 to fCircleInfos.Count -1 do + with PCircleInfo(fCircleInfos[i])^ do + begin + if GetAlpha(BrushClr) > 0.1 then + Add(Format(' ', + [center.X * scale + offsetX, center.Y * scale + offsetY, + radius, ColorToHtml(PenClr), PenWidth, + ColorToHtml(BrushClr), GetAlpha(BrushClr)], formatSettings)) + else + Add(Format(' ', + [center.X * scale + offsetX, center.Y * scale + offsetY, + radius, ColorToHtml(PenClr), PenWidth, + GetAlpha(PenClr)], formatSettings)); + end; + + for i := 0 to fTextInfos.Count -1 do + with PTextInfo(fTextInfos[i])^ do + begin + Add(Format( + ' ' + + '%s', + [boldFont[Bold], Round(fontSize * scale), + ColorToHtml(fontColor), + (x) * scale + offsetX, + (y) * scale + offsetY, text], formatSettings)); + end; + + Add(''#10); + sl.SaveToFile(filename); + Result := true; + finally + sl.free; + end; +end; +//------------------------------------------------------------------------------ + +procedure AddSubjects(svg: TSvgWriter; const paths: CPaths64; showCoords: Boolean = false); +begin + svg.AddPaths(paths, false, $200099FF, $800066FF, 1.0, showCoords); +end; +//------------------------------------------------------------------------------ + +procedure AddOpenSubjects(svg: TSvgWriter; const paths: CPaths64); +begin + svg.AddPaths(paths, true, $0, $800066FF, 2.2); +end; +//------------------------------------------------------------------------------ + +procedure AddClips(svg: TSvgWriter; const paths: CPaths64; showCoords: Boolean = false); +begin + svg.AddPaths(paths, false, $10FF9900, $80FF6600, 1.0, showCoords); +end; +//------------------------------------------------------------------------------ + +procedure AddSolution(svg: TSvgWriter; const paths: CPaths64; showCoords: Boolean); +begin + svg.AddPaths(paths, false, $8066FF66, $FF006600, 1.0, showCoords); +end; +//------------------------------------------------------------------------------ + +procedure AddOpenSolution(svg: TSvgWriter; const paths: CPaths64); +begin + svg.AddPaths(paths, true, $0, $FF006600, 1.5); +end; +//------------------------------------------------------------------------------ + +procedure SaveSvg(svg: TSvgWriter; const filename: string; + width: integer = 0; height: integer = 0; margin: integer = 0); +begin + svg.SaveToFile(filename, width, height, margin); +end; +//------------------------------------------------------------------------------ + +procedure AddSubjects(svg: TSvgWriter; const paths: CPathsD; showCoords: Boolean = false); +begin + svg.AddPaths(paths, false, $200099FF, $800066FF, 1.0, showCoords); +end; +//------------------------------------------------------------------------------ + +procedure AddOpenSubjects(svg: TSvgWriter; const paths: CPathsD); +begin + svg.AddPaths(paths, true, $0, $400066FF, 2.2); +end; +//------------------------------------------------------------------------------ + +procedure AddClips(svg: TSvgWriter; const paths: CPathsD; showCoords: Boolean = false); +begin + svg.AddPaths(paths, false, $10FF9900, $80FF6600, 1.0, showCoords); +end; +//------------------------------------------------------------------------------ + +procedure AddSolution(svg: TSvgWriter; const paths: CPathsD; showCoords: Boolean); +begin + svg.AddPaths(paths, false, $8066FF66, $FF006600, 1.0, showCoords); +end; +//------------------------------------------------------------------------------ + +procedure AddOpenSolution(svg: TSvgWriter; const paths: CPathsD); +begin + svg.AddPaths(paths, true, $0, $FF006600, 1.5); +end; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +procedure ShowSvg(const svgFilename: string); +begin + ShellExecute(0, 'open',PChar(svgFilename), nil, nil, SW_SHOW); +end; +//------------------------------------------------------------------------------ + +procedure DisplaySVG(const sub, subo, clp, sol, solo: CPathsD; + const svgName: string; width: integer = displayWidth; + height: integer = displayHeight; showCoords: Boolean = false); +var + i,j, idx, pathsCnt, pathLen: integer; + x,y,z: double; + svg: TSvgWriter; +begin + svg := TSvgWriter.Create(TFillRule.frNonZero); + try + AddSubjects(svg, sub); + AddOpenSubjects(svg, subo); + AddClips(svg, clp); + AddSolution(svg, sol, showCoords); +{$IFDEF USINGZ} + if Assigned(sol) then + begin + pathsCnt := Round(sol[1]); + idx := 2; + for i := 0 to pathsCnt -1 do + begin + pathLen := Round(sol[idx]); inc(idx, 2); + for j := 0 to pathLen -1 do + begin + x := sol[idx]; inc(idx); + y := sol[idx]; inc(idx); + z := sol[idx]; inc(idx); + if z <= 0 then Continue; + svg.AddCircle(PointD(x,y), 3, $FFFFFF00, $FF000000, 1); + svg.AddText(format('%1.0n',[z]), X +1, Y, 3); + end; + end; + end; +{$ENDIF} + AddOpenSolution(svg, solo); + SaveSvg(svg, svgName, width, height); + ShowSvg(svgName); + finally + svg.Free; + end; +end; +//------------------------------------------------------------------------------ + +procedure DisplaySVG(const sub, subo, clp, sol, solo: CPaths64; + const svgName: string; width: integer = displayWidth; + height: integer = displayHeight; showCoords: Boolean = false); +var + i,j, idx, pathsCnt, pathLen: integer; + x,y,z: Int64; + svg: TSvgWriter; +begin + svg := TSvgWriter.Create(frNonZero); + try + AddSubjects(svg, sub); + AddOpenSubjects(svg, subo); + AddClips(svg, clp); + AddSolution(svg, sol, showCoords); +{$IFDEF USINGZ} + if Assigned(sol) then + begin + pathsCnt := sol[1]; + idx := 2; + for i := 0 to pathsCnt -1 do + begin + pathLen := sol[idx]; inc(idx, 2); + for j := 0 to pathLen -1 do + begin + x := sol[idx]; inc(idx); + y := sol[idx]; inc(idx); + z := sol[idx]; inc(idx); + if z <= 0 then Continue; + svg.AddCircle(PointD(x,y), 3, $FFFFFF00, $FF000000, 1); + svg.AddText(format('%d',[z]), X +1, Y, 3); + end; + end; + end; +{$ENDIF} + AddOpenSolution(svg, solo); + SaveSvg(svg, svgName, width, height); + ShowSvg(svgName); + finally + svg.Free; + end; +end; +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + + +end. + diff --git a/DLL/TEST_APPS/Delphi_TestApps/TestApp1/Test1_DLL.dpr b/DLL/TEST_APPS/Delphi_TestApps/TestApp1/Test1_DLL.dpr new file mode 100644 index 00000000..a359386c --- /dev/null +++ b/DLL/TEST_APPS/Delphi_TestApps/TestApp1/Test1_DLL.dpr @@ -0,0 +1,135 @@ +program Test1_DLL; + +{$APPTYPE CONSOLE} +{$R *.res} + +uses + Windows, + Math, + SysUtils, + Clipper2DllCore in 'Clipper2DllCore.pas', + Clipper2DllSVG in 'Clipper2DllSVG.pas'; + +//////////////////////////////////////////////////////// +// Z callback functions +//////////////////////////////////////////////////////// + +{$IFDEF USINGZ} +var + counter64: Int64; + counterD: double; + +procedure MyZCallback64(const bot1, top1, bot2, top2: TPoint64; + var intersectPt: TPoint64); +begin + // when ztype = double, we would need to perform + // a static cast from Int64 to double + //if TypeHandle(ztype) = TypeHandle(double) then + // intersectPt.Z := PDouble(Pointer(@counter64))^ else + intersectPt.Z := counter64; + + counter64 := counter64 +1; +end; + +procedure MyZCallbackD(const bot1, top1, bot2, top2: TPointD; + var intersectPt: TPointD); +begin + // when ztype = Int64 then we need to perform + // a static cast from double to Int64 + //if (TypeHandle(ztype) = TypeHandle(double)) then + // intersectPt.Z := counterD else + intersectPt.Z := PInt64(Pointer(@counterD))^; + + counterD := counterD +1; +end; +{$ENDIF} + +//////////////////////////////////////////////////////// +// test procedures +//////////////////////////////////////////////////////// + +procedure Test_BooleanOp64; +var + csub, cclp: CPaths64; + csol_extern, csolo_extern: CPaths64; +begin + // setup + csolo_extern := nil; + WriteLn(#10'Testing BooleanOp64'); + +{$IFDEF USINGZ} + csub := MakeCPaths64([100,50,0, 10,79,0, 65,2,0, 65,98,0, 10,21,0]); + cclp := MakeCPaths64([80,50,0, 59,79,0, 26,68,0, 26,32,0, 59,21,0]); + SetZCallback64(MyZCallback64); + counter64 := 10; +{$ELSE} + csub := MakeCPaths64([100,50, 10,79, 65,2, 65,98, 10,21]); + cclp := MakeCPaths64([80,50, 59,79, 26,68, 26,32, 59,21]); +{$ENDIF} + + // do the DLL operation + BooleanOp64(Intersection, NonZero, + csub, nil, cclp, csol_extern, csolo_extern); + + DisplaySVG(csub, nil, cclp, csol_extern, nil, + 'BooleanOp64.svg', 400,400, false); + + // clean up + DisposeLocalArray64(csub); + DisposeLocalArray64(cclp); + DisposeExportedArray64(csol_extern); + DisposeExportedArray64(csolo_extern); +end; + +procedure Test_BooleanOpD; +var + csub, cclp: CPathsD; + csol_extern, csolo_extern: CPathsD; +begin + // setup + csolo_extern := nil; + WriteLn(#10'Testing BooleanOpD'); +{$IFDEF USINGZ} + csub := MakeCPathsD([100,50,0, 10,79,0, 65,2,0, 65,98,0, 10,21,0]); + cclp := MakeCPathsD([80,50,0, 59,79,0, 26,68,0, 26,32,0, 59,21,0]); + SetZCallbackD(MyZCallbackD); + counterD := 10; +{$ELSE} + csub := MakeCPathsD([100,50, 10,79, 65,2, 65,98, 10,21]); + cclp := MakeCPathsD([80,50, 59,79, 26,68, 26,32, 59,21]); +{$ENDIF} + + // do the DLL operation + BooleanOpD(Uint8(TClipType.ctIntersection), Uint8(TFillRule.frNonZero), + csub, nil, cclp, csol_extern, csolo_extern); + + // optionally display result on the console + //WriteCPaths64(csol_extern); + + DisplaySVG(csub, nil, cclp, csol_extern, nil, + 'BooleanOpD.svg', 400,400, false); + + DisposeLocalArrayD(csub); + DisposeLocalArrayD(cclp); + DisposeExportedArrayD(csol_extern); + DisposeExportedArrayD(csolo_extern); +end; + +//////////////////////////////////////////////////////// +// main entry here +//////////////////////////////////////////////////////// + +var + s: string; +begin + Randomize; + + Write(#10'Clipper2 DLL version: '); + WriteLn(Version); + + Test_BooleanOp64; + Test_BooleanOpD; + + WriteLn(#10'Press Enter to quit.'); + ReadLn(s); +end. diff --git a/DLL/TEST_APPS/Delphi_TestApps/TestApp1/Test1_DLL.dproj b/DLL/TEST_APPS/Delphi_TestApps/TestApp1/Test1_DLL.dproj new file mode 100644 index 00000000..5c913e9a --- /dev/null +++ b/DLL/TEST_APPS/Delphi_TestApps/TestApp1/Test1_DLL.dproj @@ -0,0 +1,189 @@ + + + {09E2139C-6702-4081-A629-5FF300006048} + Test1_DLL.dpr + True + Debug + Test1_DLL + 2 + Console + None + 20.1 + Win64 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + Test1_DLL + 3081 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;Winapi;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + Debug + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + true + true + true + true + true + true + true + true + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_192x192.png + activity-1.7.2.dex.jar;annotation-experimental-1.3.0.dex.jar;annotation-jvm-1.6.0.dex.jar;annotations-13.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;billing-6.0.1.dex.jar;biometric-1.1.0.dex.jar;browser-1.4.0.dex.jar;cloud-messaging.dex.jar;collection-1.1.0.dex.jar;concurrent-futures-1.1.0.dex.jar;core-1.10.1.dex.jar;core-common-2.2.0.dex.jar;core-ktx-1.10.1.dex.jar;core-runtime-2.2.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;error_prone_annotations-2.9.0.dex.jar;exifinterface-1.3.6.dex.jar;firebase-annotations-16.2.0.dex.jar;firebase-common-20.3.1.dex.jar;firebase-components-17.1.0.dex.jar;firebase-datatransport-18.1.7.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-encoders-proto-16.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.1.3.dex.jar;firebase-installations-interop-17.1.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-23.1.2.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;kotlin-stdlib-1.8.22.dex.jar;kotlin-stdlib-common-1.8.22.dex.jar;kotlin-stdlib-jdk7-1.8.22.dex.jar;kotlin-stdlib-jdk8-1.8.22.dex.jar;kotlinx-coroutines-android-1.6.4.dex.jar;kotlinx-coroutines-core-jvm-1.6.4.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.6.1.dex.jar;lifecycle-livedata-2.6.1.dex.jar;lifecycle-livedata-core-2.6.1.dex.jar;lifecycle-runtime-2.6.1.dex.jar;lifecycle-service-2.6.1.dex.jar;lifecycle-viewmodel-2.6.1.dex.jar;lifecycle-viewmodel-savedstate-2.6.1.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;okio-jvm-3.4.0.dex.jar;play-services-ads-22.2.0.dex.jar;play-services-ads-base-22.2.0.dex.jar;play-services-ads-identifier-18.0.0.dex.jar;play-services-ads-lite-22.2.0.dex.jar;play-services-appset-16.0.1.dex.jar;play-services-base-18.1.0.dex.jar;play-services-basement-18.1.0.dex.jar;play-services-cloud-messaging-17.0.1.dex.jar;play-services-location-21.0.1.dex.jar;play-services-maps-18.1.0.dex.jar;play-services-measurement-base-20.1.2.dex.jar;play-services-measurement-sdk-api-20.1.2.dex.jar;play-services-stats-17.0.2.dex.jar;play-services-tasks-18.0.2.dex.jar;print-1.0.0.dex.jar;profileinstaller-1.3.0.dex.jar;room-common-2.2.5.dex.jar;room-runtime-2.2.5.dex.jar;savedstate-1.2.1.dex.jar;sqlite-2.1.0.dex.jar;sqlite-framework-2.1.0.dex.jar;startup-runtime-1.1.1.dex.jar;tracing-1.0.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.1.8.dex.jar;transport-runtime-3.1.8.dex.jar;user-messaging-platform-2.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.7.0.dex.jar + + + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_192x192.png + activity-1.7.2.dex.jar;annotation-experimental-1.3.0.dex.jar;annotation-jvm-1.6.0.dex.jar;annotations-13.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;billing-6.0.1.dex.jar;biometric-1.1.0.dex.jar;browser-1.4.0.dex.jar;cloud-messaging.dex.jar;collection-1.1.0.dex.jar;concurrent-futures-1.1.0.dex.jar;core-1.10.1.dex.jar;core-common-2.2.0.dex.jar;core-ktx-1.10.1.dex.jar;core-runtime-2.2.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;error_prone_annotations-2.9.0.dex.jar;exifinterface-1.3.6.dex.jar;firebase-annotations-16.2.0.dex.jar;firebase-common-20.3.1.dex.jar;firebase-components-17.1.0.dex.jar;firebase-datatransport-18.1.7.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-encoders-proto-16.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.1.3.dex.jar;firebase-installations-interop-17.1.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-23.1.2.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;kotlin-stdlib-1.8.22.dex.jar;kotlin-stdlib-common-1.8.22.dex.jar;kotlin-stdlib-jdk7-1.8.22.dex.jar;kotlin-stdlib-jdk8-1.8.22.dex.jar;kotlinx-coroutines-android-1.6.4.dex.jar;kotlinx-coroutines-core-jvm-1.6.4.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.6.1.dex.jar;lifecycle-livedata-2.6.1.dex.jar;lifecycle-livedata-core-2.6.1.dex.jar;lifecycle-runtime-2.6.1.dex.jar;lifecycle-service-2.6.1.dex.jar;lifecycle-viewmodel-2.6.1.dex.jar;lifecycle-viewmodel-savedstate-2.6.1.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;okio-jvm-3.4.0.dex.jar;play-services-ads-22.2.0.dex.jar;play-services-ads-base-22.2.0.dex.jar;play-services-ads-identifier-18.0.0.dex.jar;play-services-ads-lite-22.2.0.dex.jar;play-services-appset-16.0.1.dex.jar;play-services-base-18.1.0.dex.jar;play-services-basement-18.1.0.dex.jar;play-services-cloud-messaging-17.0.1.dex.jar;play-services-location-21.0.1.dex.jar;play-services-maps-18.1.0.dex.jar;play-services-measurement-base-20.1.2.dex.jar;play-services-measurement-sdk-api-20.1.2.dex.jar;play-services-stats-17.0.2.dex.jar;play-services-tasks-18.0.2.dex.jar;print-1.0.0.dex.jar;profileinstaller-1.3.0.dex.jar;room-common-2.2.5.dex.jar;room-runtime-2.2.5.dex.jar;savedstate-1.2.1.dex.jar;sqlite-2.1.0.dex.jar;sqlite-framework-2.1.0.dex.jar;startup-runtime-1.1.1.dex.jar;tracing-1.0.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.1.8.dex.jar;transport-runtime-3.1.8.dex.jar;user-messaging-platform-2.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.7.0.dex.jar + + + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + Test1_DLL_Icon.ico + + + Test1_DLL_Icon.ico + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + USINGZ;$(DCC_Define) + (None) + none + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + DEBUG;$(DCC_Define) + false + true + true + true + + + Debug + + + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + Test1_DLL_Icon.ico + (None) + none + + + + MainSource + + + + + Base + + + Cfg_1 + Base + + + Cfg_2 + Base + + + + Delphi.Personality.12 + + + + + Test1_DLL.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + False + False + False + True + + + 12 + + + + diff --git a/Delphi/Clipper2Lib/Clipper.Core.pas b/Delphi/Clipper2Lib/Clipper.Core.pas index a6ee145c..77bc2761 100644 --- a/Delphi/Clipper2Lib/Clipper.Core.pas +++ b/Delphi/Clipper2Lib/Clipper.Core.pas @@ -2,12 +2,12 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 13 May 2024 * -* Website : http://www.angusj.com * +* Date : 22 November 2024 * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2024 * * Purpose : Core Clipper Library module * * Contains structures and functions used throughout the library * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************) {$I Clipper.inc} @@ -18,12 +18,15 @@ interface SysUtils, Classes, Math; type +{$IFDEF USINGZ} + ZType = Int64; // or alternatively, ZType = double +{$ENDIF} PPoint64 = ^TPoint64; TPoint64 = record X, Y: Int64; {$IFDEF USINGZ} - Z: Int64; + Z: ZType; {$ENDIF} end; @@ -31,7 +34,7 @@ TPoint64 = record TPointD = record X, Y: double; {$IFDEF USINGZ} - Z: Int64; + Z: ZType; {$ENDIF} end; @@ -121,6 +124,7 @@ TListEx = class fCount : integer; fCapacity : integer; fList : TPointerList; + fSorted : Boolean; protected function UnsafeGet(idx: integer): Pointer; // no range checking procedure UnsafeSet(idx: integer; val: Pointer); @@ -130,14 +134,16 @@ TListEx = class destructor Destroy; override; procedure Clear; virtual; function Add(item: Pointer): integer; + procedure DeleteLast; procedure Swap(idx1, idx2: integer); - procedure Sort(Compare: TListSortCompare); + procedure Sort(Compare: TListSortCompareFunc); procedure Resize(count: integer); property Count: integer read fCount; + property Sorted: Boolean read fSorted; property Item[idx: integer]: Pointer read UnsafeGet; default; end; - TClipType = (ctNone, ctIntersection, ctUnion, ctDifference, ctXor); + TClipType = (ctNoClip, ctIntersection, ctUnion, ctDifference, ctXor); TPointInPolygonResult = (pipOn, pipInside, pipOutside); @@ -186,11 +192,11 @@ function PointsNearEqual(const pt1, pt2: TPointD; distanceSqrd: double): Boolean {$IFDEF INLINING} inline; {$ENDIF} {$IFDEF USINGZ} -function Point64(const X, Y: Int64; Z: Int64 = 0): TPoint64; overload; +function Point64(const X, Y: Int64; Z: ZType = 0): TPoint64; overload; {$IFDEF INLINING} inline; {$ENDIF} -function Point64(const X, Y: Double; Z: Int64 = 0): TPoint64; overload; +function Point64(const X, Y: Double; Z: ZType = 0): TPoint64; overload; {$IFDEF INLINING} inline; {$ENDIF} -function PointD(const X, Y: Double; Z: Int64 = 0): TPointD; overload; +function PointD(const X, Y: Double; Z: ZType = 0): TPointD; overload; {$IFDEF INLINING} inline; {$ENDIF} {$ELSE} function Point64(const X, Y: Int64): TPoint64; overload; {$IFDEF INLINING} inline; {$ENDIF} @@ -540,6 +546,7 @@ procedure TListEx.Clear; fList := nil; fCount := 0; fCapacity := 0; + fSorted := false; end; //------------------------------------------------------------------------------ @@ -555,6 +562,13 @@ function TListEx.Add(item: Pointer): integer; fList[fCount] := item; Result := fCount; inc(fCount); + fSorted := false; +end; +//------------------------------------------------------------------------------ + +procedure TListEx.DeleteLast; +begin + dec(fCount); end; //------------------------------------------------------------------------------ @@ -611,10 +625,11 @@ procedure QuickSort(SortList: TPointerList; L, R: Integer; end; //------------------------------------------------------------------------------ -procedure TListEx.Sort(Compare: TListSortCompare); +procedure TListEx.Sort(Compare: TListSortCompareFunc); begin if fCount < 2 then Exit; QuickSort(FList, 0, fCount - 1, Compare); + fSorted := true; end; //------------------------------------------------------------------------------ @@ -654,6 +669,7 @@ procedure TListEx.Swap(idx1, idx2: integer); p := fList[idx1]; fList[idx1] := fList[idx2]; fList[idx2] := p; + fSorted := false; end; //------------------------------------------------------------------------------ @@ -1383,7 +1399,7 @@ function ArrayOfPathsToPaths(const ap: TArrayOfPaths): TPaths64; //------------------------------------------------------------------------------ {$IFDEF USINGZ} -function Point64(const X, Y: Int64; Z: Int64): TPoint64; +function Point64(const X, Y: Int64; Z: ZType): TPoint64; begin Result.X := X; Result.Y := Y; @@ -1391,7 +1407,7 @@ function Point64(const X, Y: Int64; Z: Int64): TPoint64; end; //------------------------------------------------------------------------------ -function Point64(const X, Y: Double; Z: Int64): TPoint64; +function Point64(const X, Y: Double; Z: ZType): TPoint64; begin Result.X := Round(X); Result.Y := Round(Y); @@ -1399,7 +1415,7 @@ function Point64(const X, Y: Double; Z: Int64): TPoint64; end; //------------------------------------------------------------------------------ -function PointD(const X, Y: Double; Z: Int64): TPointD; +function PointD(const X, Y: Double; Z: ZType): TPointD; begin Result.X := X; Result.Y := Y; diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index 950e847c..e66db765 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,11 +2,11 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 27 April 2024 * -* Website : http://www.angusj.com * +* Date : 22 November 2024 * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2024 * * Purpose : This is the main polygon clipping module * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************) interface @@ -219,7 +219,7 @@ TClipperBase = class FSucceeded : Boolean; FReverseSolution : Boolean; {$IFDEF USINGZ} - fDefaultZ : Int64; + fDefaultZ : Ztype; fZCallback : TZCallback64; {$ENDIF} procedure Reset; @@ -287,7 +287,7 @@ TClipperBase = class {$IFDEF USINGZ} procedure SetZ( e1, e2: PActive; var intersectPt: TPoint64); property ZCallback : TZCallback64 read fZCallback write fZCallback; - property DefaultZ : Int64 read fDefaultZ write fDefaultZ; + property DefaultZ : Ztype read fDefaultZ write fDefaultZ; {$ENDIF} property Succeeded : Boolean read FSucceeded; public @@ -372,8 +372,7 @@ TClipperD = class(TClipperBase) // for floating point coordinates FInvScale: double; {$IFDEF USINGZ} fZCallback : TZCallbackD; - procedure ZCB(const bot1, top1, bot2, top2: TPoint64; - var intersectPt: TPoint64); + procedure ZCB(const bot1, top1, bot2, top2: TPoint64; var intersectPt: TPoint64); procedure CheckCallback; {$ENDIF} public @@ -900,7 +899,7 @@ function PointInOpPolygon(const pt: TPoint64; op: POutPt): TPointInPolygonResult while (op2 <> op) and (op2.pt.Y > pt.Y) do op2 := op2.next; if (op2 = op) then break; - // must have touched or crossed the pt.Y horizonal + // must have touched or crossed the pt.Y horizontal // and this must happen an even number of times if (op2.pt.Y = pt.Y) then // touching the horizontal @@ -1017,6 +1016,11 @@ procedure AddPathsToVertexList(const paths: TPaths64; GetMem(v, sizeof(TVertex) * totalVerts); vertexList.Add(v); + {$IF not defined(FPC) and (CompilerVersion <= 26.0)} + // Delphi 7-XE5 have a problem with "continue" and the + // code analysis, marking "ascending" as "not initialized" + ascending := False; + {$IFEND} for i := 0 to High(paths) do begin len := Length(paths[i]); @@ -2559,9 +2563,8 @@ procedure TClipperBase.IntersectEdges(e1, e2: PActive; pt: TPoint64); var e1WindCnt, e2WindCnt, e1WindCnt2, e2WindCnt2: Integer; e3: PActive; - resultOp, op2: POutPt; + op, op2: POutPt; begin - resultOp := nil; // MANAGE OPEN PATH INTERSECTIONS SEPARATELY ... if FHasOpenPaths and (IsOpen(e1) or IsOpen(e2)) then begin @@ -2586,7 +2589,7 @@ procedure TClipperBase.IntersectEdges(e1, e2: PActive; pt: TPoint64); // toggle contribution ... if IsHotEdge(e1) then begin - resultOp := AddOutPt(e1, pt); + op := AddOutPt(e1, pt); if IsFront(e1) then e1.outrec.frontE := nil else e1.outrec.backE := nil; @@ -2610,12 +2613,12 @@ procedure TClipperBase.IntersectEdges(e1, e2: PActive; pt: TPoint64); SetSides(e3.outrec, e3, e1); Exit; end else - resultOp := StartOpenPath(e1, pt); + op := StartOpenPath(e1, pt); end else - resultOp := StartOpenPath(e1, pt); + op := StartOpenPath(e1, pt); {$IFDEF USINGZ} - SetZ(e1, e2, resultOp.pt); + SetZ(e1, e2, op.pt); {$ENDIF} Exit; end; @@ -2679,20 +2682,20 @@ procedure TClipperBase.IntersectEdges(e1, e2: PActive; pt: TPoint64); if not (e1WindCnt in [0,1]) or not (e2WindCnt in [0,1]) or (not IsSamePolyType(e1, e2) and (fClipType <> ctXor)) then begin - resultOp := AddLocalMaxPoly(e1, e2, pt); + op := AddLocalMaxPoly(e1, e2, pt); {$IFDEF USINGZ} - if Assigned(Result) then SetZ(e1, e2, Result.pt); + if Assigned(op) then SetZ(e1, e2, op.pt); {$ENDIF} end else if IsFront(e1) or (e1.outrec = e2.outrec) then begin // this 'else if' condition isn't strictly needed but - // it's sensible to split polygons that ony touch at + // it's sensible to split polygons that only touch at // a common vertex (not at common edges). - resultOp := AddLocalMaxPoly(e1, e2, pt); + op := AddLocalMaxPoly(e1, e2, pt); {$IFDEF USINGZ} op2 := AddLocalMinPoly(e1, e2, pt); - if Assigned(Result) then SetZ(e1, e2, Result.pt); + if Assigned(op) then SetZ(e1, e2, op.pt); SetZ(e1, e2, op2.pt); {$ELSE} AddLocalMinPoly(e1, e2, pt); @@ -2700,10 +2703,10 @@ procedure TClipperBase.IntersectEdges(e1, e2: PActive; pt: TPoint64); end else begin // can't treat as maxima & minima - resultOp := AddOutPt(e1, pt); + op := AddOutPt(e1, pt); {$IFDEF USINGZ} op2 := AddOutPt(e2, pt); - SetZ(e1, e2, Result.pt); + SetZ(e1, e2, op.pt); SetZ(e1, e2, op2.pt); {$ELSE} AddOutPt(e2, pt); @@ -2715,17 +2718,17 @@ procedure TClipperBase.IntersectEdges(e1, e2: PActive; pt: TPoint64); // if one or other edge is 'hot' ... else if IsHotEdge(e1) then begin - resultOp := AddOutPt(e1, pt); + op := AddOutPt(e1, pt); {$IFDEF USINGZ} - SetZ(e1, e2, Result.pt); + SetZ(e1, e2, op.pt); {$ENDIF} SwapOutRecs(e1, e2); end else if IsHotEdge(e2) then begin - resultOp := AddOutPt(e2, pt); + op := AddOutPt(e2, pt); {$IFDEF USINGZ} - SetZ(e1, e2, Result.pt); + SetZ(e1, e2, op.pt); {$ENDIF} SwapOutRecs(e1, e2); end @@ -2753,32 +2756,32 @@ procedure TClipperBase.IntersectEdges(e1, e2: PActive; pt: TPoint64); if not IsSamePolyType(e1, e2) then begin - resultOp := AddLocalMinPoly(e1, e2, pt, false); + op := AddLocalMinPoly(e1, e2, pt, false); {$IFDEF USINGZ} - SetZ(e1, e2, Result.pt); + SetZ(e1, e2, op.pt); {$ENDIF} end else if (e1WindCnt = 1) and (e2WindCnt = 1) then begin - resultOp := nil; + op := nil; case FClipType of ctIntersection: if (e1WindCnt2 <= 0) or (e2WindCnt2 <= 0) then Exit - else resultOp := AddLocalMinPoly(e1, e2, pt, false); + else op := AddLocalMinPoly(e1, e2, pt, false); ctUnion: if (e1WindCnt2 <= 0) and (e2WindCnt2 <= 0) then - resultOp := AddLocalMinPoly(e1, e2, pt, false); + op := AddLocalMinPoly(e1, e2, pt, false); ctDifference: if ((GetPolyType(e1) = ptClip) and (e1WindCnt2 > 0) and (e2WindCnt2 > 0)) or ((GetPolyType(e1) = ptSubject) and (e1WindCnt2 <= 0) and (e2WindCnt2 <= 0)) then - resultOp := AddLocalMinPoly(e1, e2, pt, false); + op := AddLocalMinPoly(e1, e2, pt, false); else // xOr - resultOp := AddLocalMinPoly(e1, e2, pt, false); + op := AddLocalMinPoly(e1, e2, pt, false); end; {$IFDEF USINGZ} - if assigned(Result) then SetZ(e1, e2, Result.pt); + if assigned(op) then SetZ(e1, e2, op.pt); {$ENDIF} end; end; @@ -2842,7 +2845,7 @@ procedure TClipperBase.ExecuteInternal(clipType: TClipType; Y: Int64; e: PActive; begin - if clipType = ctNone then Exit; + if clipType = ctNoClip then Exit; FFillRule := fillRule; FClipType := clipType; Reset; @@ -3525,7 +3528,7 @@ procedure TClipperBase.DoHorizontal(horzEdge: PActive); end; if IsHotEdge(horzEdge) then begin - //nb: The outrec containining the op returned by IntersectEdges + //nb: The outrec containing the op returned by IntersectEdges //above may no longer be associated with horzEdge. FHorzSegList.Add(GetLastOp(horzEdge)); end; diff --git a/Delphi/Clipper2Lib/Clipper.Minkowski.pas b/Delphi/Clipper2Lib/Clipper.Minkowski.pas index bacb3ea2..d2e97055 100644 --- a/Delphi/Clipper2Lib/Clipper.Minkowski.pas +++ b/Delphi/Clipper2Lib/Clipper.Minkowski.pas @@ -5,7 +5,7 @@ * Date : 21 December 2023 * * Copyright : Angus Johnson 2010-2022 * * Purpose : Minkowski Addition and Difference * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************) {$I Clipper.inc} diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index 7e7d99f0..e9c474ba 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,11 +2,11 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 17 April 2024 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2024 * +* Date : 22 January 2025 * +* Website : https://www.angusj.com * +* Copyright : Angus Johnson 2010-2025 * * Purpose : Path Offset (Inflate/Shrink) * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************) {$I Clipper.inc} @@ -72,13 +72,14 @@ TClipperOffset = class fZCallback64 : TZCallback64; procedure ZCB(const bot1, top1, bot2, top2: TPoint64; var intersectPt: TPoint64); - procedure AddPoint(x,y: double; z: Int64); overload; + procedure AddPoint(x,y: double; z: ZType); overload; procedure AddPoint(const pt: TPoint64); overload; {$IFDEF INLINING} inline; {$ENDIF} - procedure AddPoint(const pt: TPoint64; newZ: Int64); overload; + procedure AddPoint(const pt: TPoint64; newZ: ZType); overload; {$IFDEF INLINING} inline; {$ENDIF} {$ELSE} procedure AddPoint(x,y: double); overload; + procedure AddPoint(const pt: TPoint64); overload; {$IFDEF INLINING} inline; {$ENDIF} {$ENDIF} @@ -141,6 +142,20 @@ implementation TwoPi : Double = 2 * PI; InvTwoPi : Double = 1/(2 * PI); +// 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 arc_const = 0.002; // <-- 1/500 + //------------------------------------------------------------------------------ // Miscellaneous offset support functions //------------------------------------------------------------------------------ @@ -236,9 +251,7 @@ function UnsafeGet(List: TList; Index: Integer): Pointer; constructor TGroup.Create(const pathsIn: TPaths64; jt: TJoinType; et: TEndType); var i, len: integer; - a: double; isJoined: boolean; - pb: PBoolean; begin Self.joinType := jt; Self.endType := et; @@ -345,7 +358,6 @@ procedure TClipperOffset.DoGroupOffset(group: TGroup); i,j, len, steps: Integer; r, stepsPer360, arcTol: Double; absDelta: double; - isShrinking: Boolean; rec: TRect64; pt0: TPoint64; begin @@ -366,13 +378,12 @@ procedure TClipperOffset.DoGroupOffset(group: TGroup); if (group.joinType = jtRound) or (group.endType = etRound) then begin // calculate the number of steps required to approximate a circle - // (see http://www.angusj.com/clipper2/Docs/Trigonometry.htm) + // (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. - arcTol := Iif(fArcTolerance > 0.01, - Min(absDelta, fArcTolerance), - Log10(2 + absDelta) * 0.25); // empirically derived + arcTol := Iif(fArcTolerance > 0.0, + Min(absDelta, fArcTolerance), absDelta * arc_const); stepsPer360 := Pi / ArcCos(1 - arcTol / absDelta); if (stepsPer360 > absDelta * Pi) then @@ -450,6 +461,7 @@ procedure TClipperOffset.BuildNormals; begin len := Length(fInPath); SetLength(fNorms, len); + if len = 0 then Exit; for i := 0 to len-2 do fNorms[i] := GetUnitNormal(fInPath[i], fInPath[i+1]); fNorms[len -1] := GetUnitNormal(fInPath[len -1], fInPath[0]); @@ -697,7 +709,7 @@ procedure TClipperOffset.ZCB(const bot1, top1, bot2, top2: TPoint64; //------------------------------------------------------------------------------ {$IFDEF USINGZ} -procedure TClipperOffset.AddPoint(x,y: double; z: Int64); +procedure TClipperOffset.AddPoint(x,y: double; z: ZType); {$ELSE} procedure TClipperOffset.AddPoint(x,y: double); {$ENDIF} @@ -721,7 +733,7 @@ procedure TClipperOffset.AddPoint(x,y: double); //------------------------------------------------------------------------------ {$IFDEF USINGZ} -procedure TClipperOffset.AddPoint(const pt: TPoint64; newZ: Int64); +procedure TClipperOffset.AddPoint(const pt: TPoint64; newZ: ZType); begin AddPoint(pt.X, pt.Y, newZ); end; @@ -746,7 +758,7 @@ function IntersectPoint(const ln1a, ln1b, ln2a, ln2b: TPointD): TPointD; m1,b1,m2,b2: double; begin result := NullPointD; - //see http://astronomy.swin.edu.au/~pbourke/geometry/lineline2d/ + //see https://paulbourke.net/geometry/pointlineplane/#i2l if (ln1B.X = ln1A.X) then begin if (ln2B.X = ln2A.X) then exit; //parallel lines @@ -918,10 +930,8 @@ procedure TClipperOffset.DoRound(j, k: Integer; angle: double); // when fDeltaCallback64 is assigned, fGroupDelta won't be constant, // so we'll need to do the following calculations for *every* vertex. absDelta := Abs(fGroupDelta); - arcTol := Iif(fArcTolerance > 0.01, - Min(absDelta, fArcTolerance), - Log10(2 + absDelta) * 0.25); // empirically derived - //http://www.angusj.com/clipper2/Docs/Trigonometry.htm + arcTol := Iif(fArcTolerance > 0.0, + Min(absDelta, fArcTolerance), absDelta * arc_const); stepsPer360 := Pi / ArcCos(1 - arcTol / absDelta); if (stepsPer360 > absDelta * Pi) then stepsPer360 := absDelta * Pi; // avoid excessive precision @@ -991,19 +1001,18 @@ procedure TClipperOffset.DoRound(j, k: Integer; angle: double); if (cosA > -0.999) and (sinA * fGroupDelta < 0) then begin // 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} AddPoint(GetPerpendic(fInPath[j], fNorms[k], fGroupDelta), fInPath[j].Z); -{$ELSE} - AddPoint(GetPerpendic(fInPath[j], fNorms[k], fGroupDelta)); -{$ENDIF} - // this extra point is the only simple way to ensure that path reversals - // (ie over-shrunk paths) are fully cleaned out with the trailing union op. - // However it's probably safe to skip this whenever an angle is almost flat. - if (cosA < 0.99) then - AddPoint(fInPath[j]); // (#405) -{$IFDEF USINGZ} + AddPoint(fInPath[j]); // (#405, #873) AddPoint(GetPerpendic(fInPath[j], fNorms[j], fGroupDelta), fInPath[j].Z); {$ELSE} + AddPoint(GetPerpendic(fInPath[j], fNorms[k], fGroupDelta)); + AddPoint(fInPath[j]); // (#405, #873) AddPoint(GetPerpendic(fInPath[j], fNorms[j], fGroupDelta)); {$ENDIF} end diff --git a/Delphi/Clipper2Lib/Clipper.RectClip.pas b/Delphi/Clipper2Lib/Clipper.RectClip.pas index 8a0cf05c..a6898ba8 100644 --- a/Delphi/Clipper2Lib/Clipper.RectClip.pas +++ b/Delphi/Clipper2Lib/Clipper.RectClip.pas @@ -2,11 +2,11 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 27 April 2024 * -* Website : http://www.angusj.com * +* Date : 5 July 2024 * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2024 * * Purpose : FAST rectangular clipping * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************) interface @@ -650,9 +650,28 @@ function TRectClip64.Execute(const paths: TPaths64): TPaths64; end; //------------------------------------------------------------------------------ +function StartLocsAreClockwise(const startLocs: TList): Boolean; +var + i,j, res: integer; +begin + res := 0; + for i := 1 to startLocs.Count -1 do + begin + j := Ord(TLocation(startLocs[i])) - Ord(TLocation(startLocs[i - 1])); + case j of + -1: dec(res); + 1: inc(res); + -3: inc(res); + 3: dec(res); + end; + end; + result := res > 0; +end; +//------------------------------------------------------------------------------ + procedure TRectClip64.ExecuteInternal(const path: TPath64); var - i,highI : integer; + i,j, highI : integer; prevPt,ip,ip2 : TPoint64; loc, prevLoc : TLocation; loc2 : TLocation; @@ -661,6 +680,7 @@ procedure TRectClip64.ExecuteInternal(const path: TPath64); crossingLoc : TLocation; prevCrossLoc : TLocation; isCw : Boolean; + startLocsCW : Boolean; begin if (Length(path) < 3) then Exit; fStartLocs.Clear; @@ -797,10 +817,12 @@ procedure TRectClip64.ExecuteInternal(const path: TPath64); begin // yep, the path does fully contain rect // so add rect to the solution + startLocsCW := StartLocsAreClockwise(fStartLocs); for i := 0 to 3 do begin - Add(fRectPath[i]); - AddToEdge(fEdges[i*2], fResults[0]); + if startLocsCW then j := i else j := 3 - i; + Add(fRectPath[j]); + AddToEdge(fEdges[j*2], fResults[0]); end; end; end; diff --git a/Delphi/Clipper2Lib/Clipper.inc b/Delphi/Clipper2Lib/Clipper.inc index 5b15f920..17da40df 100644 --- a/Delphi/Clipper2Lib/Clipper.inc +++ b/Delphi/Clipper2Lib/Clipper.inc @@ -7,7 +7,7 @@ {.$DEFINE USINGZ} /////////////////////////////////////////////////////////////////////////////// -//COMPILER DIFINED PREPROCESSOR DIRECTIVES (ie. do not touch ;)) +//COMPILER DEFINED PREPROCESSOR DIRECTIVES (ie. do not touch ;)) /////////////////////////////////////////////////////////////////////////////// {$IFDEF FPC} diff --git a/Delphi/Clipper2Lib/Clipper.pas b/Delphi/Clipper2Lib/Clipper.pas index 0c2fe87e..09a33f43 100644 --- a/Delphi/Clipper2Lib/Clipper.pas +++ b/Delphi/Clipper2Lib/Clipper.pas @@ -3,10 +3,10 @@ (******************************************************************************* * Author : Angus Johnson * * Date : 7 May 2024 * -* Website : http://www.angusj.com * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2024 * * Purpose : This module provides a simple interface to the Clipper Library * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************) interface @@ -52,7 +52,7 @@ interface etSquare = Clipper.Offset.etSquare; etRound = Clipper.Offset.etRound; - ctNone = Clipper.Core.ctNone; + ctNone = Clipper.Core.ctNoClip; ctIntersection = Clipper.Core.ctIntersection; ctUnion = Clipper.Core.ctUnion; ctDifference = Clipper.Core.ctDifference; @@ -821,7 +821,7 @@ function DistanceSqrd(const pt1, pt2: TPoint64): double; var x1,y1,x2,y2: double; begin - // nb: older versions of Delphi don't allow explicit typcasting + // nb: older versions of Delphi don't allow explicit typecasting x1 := pt1.X; y1 := pt1.Y; x2 := pt2.X; y2 := pt2.Y; result := Sqr(x1 - x2) + Sqr(y1 - y2); diff --git a/Delphi/Utils/Clipper.SVG.pas b/Delphi/Utils/Clipper.SVG.pas index 279d1211..146c54c8 100644 --- a/Delphi/Utils/Clipper.SVG.pas +++ b/Delphi/Utils/Clipper.SVG.pas @@ -3,10 +3,10 @@ (******************************************************************************* * Author : Angus Johnson * * Date : 21 March 2024 * -* Website : http://www.angusj.com * +* Website : https://www.angusj.com * * Copyright : Angus Johnson 2010-2024 * * Purpose : This module provides a very simple SVG Writer for Clipper2 * -* License : http://www.boost.org/LICENSE_1_0.txt * +* License : https://www.boost.org/LICENSE_1_0.txt * *******************************************************************************) interface diff --git a/README.md b/README.md index 2a7dbc54..2d6be184 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ ### A Polygon Clipping and Offsetting library (in C++, C# & Delphi)
[![GitHub Actions C++ status](https://github.com/AngusJohnson/Clipper2/actions/workflows/actions_cpp.yml/badge.svg)](https://github.com/AngusJohnson/Clipper2/actions/workflows/actions_cpp.yml) [![C#](https://github.com/AngusJohnson/Clipper2/actions/workflows/actions_csharp.yml/badge.svg)](https://github.com/AngusJohnson/Clipper2/actions/workflows/actions_csharp.yml) [![License](https://img.shields.io/badge/License-Boost_1.0-lightblue.svg)](https://www.boost.org/LICENSE_1_0.txt) [![Nuget](https://img.shields.io/nuget/v/Clipper2?color=green)](https://www.nuget.org/packages/Clipper2) -[![documentation](https://user-images.githubusercontent.com/5280692/187832279-b2a43890-da80-4888-95fe-793f092be372.svg)](http://www.angusj.com/clipper2/Docs/Overview.htm) +[![documentation](https://user-images.githubusercontent.com/5280692/187832279-b2a43890-da80-4888-95fe-793f092be372.svg)](https://www.angusj.com/clipper2/Docs/Overview.htm) -The Clipper2 library performs **intersection**, **union**, **difference** and **XOR** boolean operations on both simple and complex polygons. It also performs polygon offsetting. This is a major update of my original Clipper library that was written over 10 years ago. That library I'm now calling Clipper1, and while it still works very well, Clipper2 is [better](http://www.angusj.com/clipper2/Docs/Changes.htm) in just about every way. +The Clipper2 library performs **intersection**, **union**, **difference** and **XOR** boolean operations on both simple and complex polygons. It also performs polygon offsetting. This is a major update of my original Clipper library that was written over 10 years ago. That library I'm now calling Clipper1, and while it still works very well, Clipper2 is [better](https://www.angusj.com/clipper2/Docs/Changes.htm) in just about every way. ### Compilers Clipper2 can be compiled using either C++, or C#, or Delphi Pascal. The library can also be accessed from other programming languages by dynamically linking to exported functions in the [C++ compiled Clipper2 library](https://github.com/AngusJohnson/Clipper2/tree/main/DLL). (Since the C++ compiled code is [measurably](https://www.angusj.com/clipper2/Docs/Changes.htm) faster, C# and Delphi developers may also prefer this approach in applications where the library's performance is critical.) @@ -16,7 +16,7 @@ The Clipper2 library performs **intersection**, **union**, **difference** ### Documentation - Extensive HTML documentation + Extensive HTML documentation

### Examples