From 4057a508586bf3c5d4e6e397904b501077231f57 Mon Sep 17 00:00:00 2001 From: angusj Date: Tue, 28 Mar 2023 23:13:17 +1000 Subject: [PATCH 01/95] Fixed a significant bug in Clipper.Engine.cs (C# only) (#470). --- CSharp/Clipper2Lib/Clipper.Engine.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 21100bf7..ead2ab80 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 3 March 2023 * +* Date : 28 March 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -3290,11 +3290,12 @@ public bool Execute(ClipType clipType, FillRule fillRule, PathsD solutionClosed) public bool Execute(ClipType clipType, FillRule fillRule, PolyTreeD polytree, PathsD openPaths) { polytree.Clear(); + openPaths.Clear(); + _using_polytree = true; (polytree as PolyPathD).Scale = _scale; #if USINGZ CheckZCallback(); #endif - openPaths.Clear(); Paths64 oPaths = new Paths64(); bool success = true; try From 0913ad98b6c7667895bd5bf34c55b0aec6d4361b Mon Sep 17 00:00:00 2001 From: philstopford Date: Wed, 29 Mar 2023 15:00:26 -0500 Subject: [PATCH 02/95] Remove unused references across C# (#472) --- CSharp/Clipper2Lib.Benchmark/Benchmarks.cs | 1 - CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs | 3 --- CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs | 3 --- CSharp/Clipper2Lib/Clipper.Core.cs | 2 -- CSharp/Clipper2Lib/Clipper.Engine.cs | 1 - CSharp/Clipper2Lib/Clipper.Minkowski.cs | 1 - CSharp/Clipper2Lib/Clipper.RectClip.cs | 1 - CSharp/Utils/SVG/Clipper.SVG.Utils.cs | 2 -- 8 files changed, 14 deletions(-) diff --git a/CSharp/Clipper2Lib.Benchmark/Benchmarks.cs b/CSharp/Clipper2Lib.Benchmark/Benchmarks.cs index 54a13979..778bb654 100644 --- a/CSharp/Clipper2Lib.Benchmark/Benchmarks.cs +++ b/CSharp/Clipper2Lib.Benchmark/Benchmarks.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Jobs; diff --git a/CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs b/CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs index 862a46a9..14e7811f 100644 --- a/CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs +++ b/CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs @@ -6,13 +6,10 @@ * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ -using System.Collections.Generic; using System.Reflection; using System.IO; using Clipper2Lib; using System; -using System.Security.Cryptography; -using System.Xml.Linq; namespace ClipperDemo1 { diff --git a/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs b/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs index d2e41132..97a28e68 100644 --- a/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs +++ b/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs @@ -6,11 +6,8 @@ * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ -using System; -using System.Collections.Generic; using System.IO; using System.Reflection; -using System.Runtime.CompilerServices; using Clipper2Lib; namespace ClipperDemo1 diff --git a/CSharp/Clipper2Lib/Clipper.Core.cs b/CSharp/Clipper2Lib/Clipper.Core.cs index 66b60add..ed834df0 100644 --- a/CSharp/Clipper2Lib/Clipper.Core.cs +++ b/CSharp/Clipper2Lib/Clipper.Core.cs @@ -11,8 +11,6 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Security.Cryptography; -using System.Threading; namespace Clipper2Lib { diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index ead2ab80..26c22498 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -14,7 +14,6 @@ using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; -using Clipper2Lib; namespace Clipper2Lib { diff --git a/CSharp/Clipper2Lib/Clipper.Minkowski.cs b/CSharp/Clipper2Lib/Clipper.Minkowski.cs index 5b379086..5b38c739 100644 --- a/CSharp/Clipper2Lib/Clipper.Minkowski.cs +++ b/CSharp/Clipper2Lib/Clipper.Minkowski.cs @@ -9,7 +9,6 @@ #nullable enable using System; -using System.Collections.Generic; namespace Clipper2Lib { diff --git a/CSharp/Clipper2Lib/Clipper.RectClip.cs b/CSharp/Clipper2Lib/Clipper.RectClip.cs index bffb144c..aed89823 100644 --- a/CSharp/Clipper2Lib/Clipper.RectClip.cs +++ b/CSharp/Clipper2Lib/Clipper.RectClip.cs @@ -10,7 +10,6 @@ #nullable enable using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; namespace Clipper2Lib diff --git a/CSharp/Utils/SVG/Clipper.SVG.Utils.cs b/CSharp/Utils/SVG/Clipper.SVG.Utils.cs index 4b14fbc0..3908abe8 100644 --- a/CSharp/Utils/SVG/Clipper.SVG.Utils.cs +++ b/CSharp/Utils/SVG/Clipper.SVG.Utils.cs @@ -6,9 +6,7 @@ * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ -using System; using System.IO; -using System.Runtime.InteropServices; namespace Clipper2Lib { From e012779edb517562a5b3bd3152a0c8fb83a365dc Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 2 Apr 2023 18:44:06 +1000 Subject: [PATCH 03/95] Fixed a bug in ExecuteRectClipLines() (C++ only) (#471) Changed a parameter in ExecuteRectClip() (C++ only) (#471) Fixed a crash in Clipper.Offset when not paths are submitted before offsetting. (C#, Del.)(#476) --- CPP/Clipper2Lib/include/clipper2/clipper.h | 20 +++++++++---------- CPP/Examples/RectClipping/RectClipping.cpp | 5 ++--- CPP/Tests/TestRectClip.cpp | 16 +++++++-------- .../Tests1/Tests/TestOffset.cs | 19 ++++++++++++++++++ CSharp/Clipper2Lib/Clipper.Offset.cs | 4 +++- Delphi/Clipper2Lib/Clipper.Offset.pas | 4 +++- 6 files changed, 45 insertions(+), 23 deletions(-) create mode 100644 CSharp/Clipper2Lib.Tests/Tests1/Tests/TestOffset.cs diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.h b/CPP/Clipper2Lib/include/clipper2/clipper.h index 6579f59c..692da199 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 23 March 2023 * +* Date : 2 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -198,7 +198,7 @@ namespace Clipper2Lib { } inline Paths64 ExecuteRectClip(const Rect64& rect, - const Paths64& paths, bool convex_only = false) + const Paths64& paths, bool convex_only) { if (rect.IsEmpty() || paths.empty()) return Paths64(); RectClip rc(rect); @@ -206,7 +206,7 @@ namespace Clipper2Lib { } inline Paths64 ExecuteRectClip(const Rect64& rect, - const Path64& path, bool convex_only = false) + const Path64& path, bool convex_only) { if (rect.IsEmpty() || path.empty()) return Paths64(); RectClip rc(rect); @@ -214,7 +214,7 @@ namespace Clipper2Lib { } inline PathsD ExecuteRectClip(const RectD& rect, - const PathsD& paths, bool convex_only = false, int precision = 2) + const PathsD& paths, bool convex_only, int precision = 2) { if (rect.IsEmpty() || paths.empty()) return PathsD(); int error_code = 0; @@ -230,7 +230,7 @@ namespace Clipper2Lib { } inline PathsD ExecuteRectClip(const RectD& rect, - const PathD& path, bool convex_only = false, int precision = 2) + const PathD& path, bool convex_only, int precision = 2) { return ExecuteRectClip(rect, PathsD{ path }, convex_only, precision); } @@ -247,11 +247,6 @@ namespace Clipper2Lib { return ExecuteRectClipLines(rect, Paths64{ line }); } - inline PathsD ExecuteRectClipLines(const RectD& rect, const PathD& line, int precision = 2) - { - return ExecuteRectClip(rect, PathsD{ line }, precision); - } - inline PathsD ExecuteRectClipLines(const RectD& rect, const PathsD& lines, int precision = 2) { if (rect.IsEmpty() || lines.empty()) return PathsD(); @@ -267,6 +262,11 @@ namespace Clipper2Lib { return ScalePaths(p, 1 / scale, error_code); } + inline PathsD ExecuteRectClipLines(const RectD& rect, const PathD& line, int precision = 2) + { + return ExecuteRectClipLines(rect, PathsD{ line }, precision); + } + namespace details { diff --git a/CPP/Examples/RectClipping/RectClipping.cpp b/CPP/Examples/RectClipping/RectClipping.cpp index 1ad2baca..13cc7c2e 100644 --- a/CPP/Examples/RectClipping/RectClipping.cpp +++ b/CPP/Examples/RectClipping/RectClipping.cpp @@ -1,7 +1,6 @@ #include - #include #include #include @@ -133,7 +132,7 @@ void DoRandomPoly(int count) sub.push_back(MakeRandomPolyD(width, height, count)); ////////////////////////////////// - sol = ExecuteRectClip(rect, sub); + sol = ExecuteRectClip(rect, sub, false); ////////////////////////////////// FillRule fr = FillRule::EvenOdd; @@ -174,7 +173,7 @@ void MeasurePerformance(int min, int max, int step) { Timer t("RectClip: "); - sol = ExecuteRectClip(rect, sub); + sol = ExecuteRectClip(rect, sub, false); } { diff --git a/CPP/Tests/TestRectClip.cpp b/CPP/Tests/TestRectClip.cpp index 24e493d8..1cf2c8c5 100644 --- a/CPP/Tests/TestRectClip.cpp +++ b/CPP/Tests/TestRectClip.cpp @@ -10,22 +10,22 @@ TEST(Clipper2Tests, TestRectClip) clp.push_back(rect.AsPath()); sub.push_back(MakePath({ 100,100, 700,100, 700,500, 100,500 })); - sol = ExecuteRectClip(rect, sub); + sol = ExecuteRectClip(rect, sub, false); EXPECT_TRUE(Area(sol) == Area(sub)); sub.clear(); sub.push_back(MakePath({ 110,110, 700,100, 700,500, 100,500 })); - sol = ExecuteRectClip(rect, sub); + sol = ExecuteRectClip(rect, sub, false); EXPECT_TRUE(Area(sol) == Area(sub)); sub.clear(); sub.push_back(MakePath({ 90,90, 700,100, 700,500, 100,500 })); - sol = ExecuteRectClip(rect, sub); + sol = ExecuteRectClip(rect, sub, false); EXPECT_TRUE(Area(sol) == Area(clp)); sub.clear(); sub.push_back(MakePath({ 110,110, 690,110, 690,490, 110,490 })); - sol = ExecuteRectClip(rect, sub); + sol = ExecuteRectClip(rect, sub, false); EXPECT_TRUE(Area(sol) == Area(sub)); sub.clear(); @@ -33,24 +33,24 @@ TEST(Clipper2Tests, TestRectClip) rect = Rect64(390, 290, 410, 310); clp.push_back(rect.AsPath()); sub.push_back(MakePath({ 410,290, 500,290, 500,310, 410,310 })); - sol = ExecuteRectClip(rect, sub); + sol = ExecuteRectClip(rect, sub, false); EXPECT_TRUE(sol.empty()); sub.clear(); sub.push_back(MakePath({ 430,290, 470,330, 390,330 })); - sol = ExecuteRectClip(rect, sub); + sol = ExecuteRectClip(rect, sub, false); EXPECT_TRUE(sol.empty()); sub.clear(); sub.push_back(MakePath({ 450,290, 480,330, 450,330 })); - sol = ExecuteRectClip(rect, sub); + sol = ExecuteRectClip(rect, sub, false); EXPECT_TRUE(sol.empty()); sub.clear(); sub.push_back(MakePath({ 208,66, 366,112, 402,303, 234,332, 233,262, 243,140, 215,126, 40,172 })); rect = Rect64(237, 164, 322, 248); - sol = ExecuteRectClip(rect, sub); + sol = ExecuteRectClip(rect, sub, false); const auto solBounds = GetBounds(sol); EXPECT_EQ(solBounds.Width(), rect.Width()); EXPECT_EQ(solBounds.Height(), rect.Height()); diff --git a/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestOffset.cs b/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestOffset.cs new file mode 100644 index 00000000..54522c74 --- /dev/null +++ b/CSharp/Clipper2Lib.Tests/Tests1/Tests/TestOffset.cs @@ -0,0 +1,19 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Clipper2Lib.UnitTests +{ + + [TestClass] + public class TestOffsets + { + + [TestMethod] + public void TestOffsetEmpty() + { + Paths64 solution = new(); + + ClipperOffset offset = new ClipperOffset(); + offset.Execute(10, solution); + } + } +} diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index a58b668a..7cb0ec89 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 22 March 2023 * +* Date : 2 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -132,6 +132,7 @@ public void Execute(double delta, Paths64 solution) { solution.Clear(); ExecuteInternal(delta); + if (_groupList.Count == 0) return; // clean up self-intersections ... Clipper64 c = new Clipper64() @@ -154,6 +155,7 @@ public void Execute(double delta, PolyTree64 polytree) { polytree.Clear(); ExecuteInternal(delta); + if (_groupList.Count == 0) return; // clean up self-intersections ... Clipper64 c = new Clipper64() diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index 1dd5210e..ae21de60 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 26 March 2023 * +* Date : 2 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -566,7 +566,9 @@ procedure TClipperOffset.Execute(delta: Double; out solution: TPaths64); group: TGroup; begin fSolution := nil; + solution := nil; ExecuteInternal(delta); + if fGroupList.Count = 0 then Exit; // clean up self-intersections ... with TClipper64.Create do From 0ce6056b6ceffbc31f5c18c92a95c2dafc2f3784 Mon Sep 17 00:00:00 2001 From: Wang Chao Date: Mon, 3 Apr 2023 20:41:31 +0800 Subject: [PATCH 04/95] perf: remove copy in offset (#481) --- .../include/clipper2/clipper.core.h | 44 +++++++++---------- CPP/Clipper2Lib/src/clipper.offset.cpp | 7 +-- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index c7522cb9..6a5f72ef 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -558,38 +558,36 @@ namespace Clipper2Lib } template - inline Path StripDuplicates(const Path& path, bool is_closed_path) + inline void StripDuplicates( Path& path, bool is_closed_path) { - if (path.size() == 0) return Path(); - Path result; - result.reserve(path.size()); - typename Path::const_iterator path_iter = path.cbegin(); - Point first_pt = *path_iter++, last_pt = first_pt; - result.push_back(first_pt); - for (; path_iter != path.cend(); ++path_iter) + if (path.size() == 0) return; + typename Path::iterator path_iter = path.begin(); + Point first_pt = *path_iter++, last_pt = first_pt; + for (; path_iter != path.end();) + { + if (*path_iter == last_pt) + path_iter = path.erase(path_iter); + else { - if (*path_iter != last_pt) - { - last_pt = *path_iter; - result.push_back(last_pt); - } + last_pt = *path_iter; + ++path_iter; } - if (!is_closed_path) return result; - while (result.size() > 1 && result.back() == first_pt) result.pop_back(); - return result; + } + if (is_closed_path) + while (path.size() > 1 && path.back() == first_pt) path.pop_back(); } template - inline Paths StripDuplicates(const Paths& paths, bool is_closed_path) + inline Paths StripDuplicates( Paths& paths, bool is_closed_path) { - Paths result; - result.reserve(paths.size()); - for (typename Paths::const_iterator paths_citer = paths.cbegin(); - paths_citer != paths.cend(); ++paths_citer) + //Paths result; + //result.reserve(paths.size()); + for (typename Paths::iterator paths_citer = paths.begin(); + paths_citer != paths.end(); ++paths_citer) { - result.push_back(StripDuplicates(*paths_citer, is_closed_path)); + StripDuplicates(*paths_citer, is_closed_path); } - return result; + return paths; } // Miscellaneous ------------------------------------------------------------ diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index 78cd8237..0fe0697c 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -481,10 +481,11 @@ void ClipperOffset::DoGroupOffset(Group& group) bool is_joined = (end_type_ == EndType::Polygon) || (end_type_ == EndType::Joined); - Paths64::const_iterator path_iter; - for(path_iter = group.paths_in.cbegin(); path_iter != group.paths_in.cend(); ++path_iter) + Paths64::iterator path_iter; + for(path_iter = group.paths_in.begin(); path_iter != group.paths_in.end(); ++path_iter) { - Path64 path = StripDuplicates(*path_iter, is_joined); + auto path = *path_iter; + StripDuplicates(path, is_joined); Path64::size_type cnt = path.size(); if (cnt == 0 || ((cnt < 3) && group.end_type == EndType::Polygon)) continue; From 1eab2525fcaa645a73a2f02c417a804b19c0f7d1 Mon Sep 17 00:00:00 2001 From: angusj Date: Mon, 3 Apr 2023 22:52:46 +1000 Subject: [PATCH 05/95] Minor tweak to StripDuplicates function (C++) (#481) --- CPP/Clipper2Lib/include/clipper2/clipper.core.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index 6a5f72ef..5d90cd59 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 22 March 2023 * +* Date : 3 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library structures and functions * @@ -578,16 +578,13 @@ namespace Clipper2Lib } template - inline Paths StripDuplicates( Paths& paths, bool is_closed_path) + inline void StripDuplicates( Paths& paths, bool is_closed_path) { - //Paths result; - //result.reserve(paths.size()); for (typename Paths::iterator paths_citer = paths.begin(); paths_citer != paths.end(); ++paths_citer) { StripDuplicates(*paths_citer, is_closed_path); } - return paths; } // Miscellaneous ------------------------------------------------------------ From 3233b2ada6df6527eec3d1dd8c6d921e347ecb0a Mon Sep 17 00:00:00 2001 From: Wang Chao Date: Tue, 4 Apr 2023 21:34:44 +0800 Subject: [PATCH 06/95] Add googlebenchmark and improve the stripduplicates (#483) * Add googlebenchmark and improve the stripduplicates * Moved file to CPP/Utils/CommonUtils.h --- CPP/BenchMark/CMakeLists.txt | 36 ++++++++ CPP/BenchMark/README.md | 6 ++ CPP/BenchMark/StripDuplicateBenchmark.cpp | 85 +++++++++++++++++++ CPP/CMakeLists.txt | 6 ++ .../include/clipper2/clipper.core.h | 19 +---- CPP/Utils/CommonUtils.h | 16 ++++ 6 files changed, 153 insertions(+), 15 deletions(-) create mode 100644 CPP/BenchMark/CMakeLists.txt create mode 100644 CPP/BenchMark/README.md create mode 100644 CPP/BenchMark/StripDuplicateBenchmark.cpp create mode 100644 CPP/Utils/CommonUtils.h diff --git a/CPP/BenchMark/CMakeLists.txt b/CPP/BenchMark/CMakeLists.txt new file mode 100644 index 00000000..16a5389e --- /dev/null +++ b/CPP/BenchMark/CMakeLists.txt @@ -0,0 +1,36 @@ +# fetch the google benchmark library +include(FetchContent) +set(BENCHMARK_ENABLE_GTEST_TESTS OFF) +set(BENCHMARK_ENABLE_TESTING OFF) +message("start fetching the googlebenchmark") +FetchContent_Declare(googlebenchmark + GIT_REPOSITORY https://github.com/google/benchmark.git + GIT_TAG v1.7.1 +) + +FetchContent_MakeAvailable( + googlebenchmark) +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +message("fetching is done") + +set(benchmark_srcs + StripDuplicateBenchmark.cpp + # more to add +) + +# add each benchmark from the benchmark_srcs +foreach(benchmark ${benchmark_srcs}) + get_filename_component(benchmark_target ${benchmark} NAME_WE) + + message(STATUS "${PROJECT_NAME} add benchmark ${benchmark_target}") + add_executable(${benchmark_target} ${benchmark}) + target_include_directories(${benchmark_target} PUBLIC + + ) + + target_link_libraries(${benchmark_target} + benchmark::benchmark + Clipper2 + Clipper2utils + ) +endforeach() diff --git a/CPP/BenchMark/README.md b/CPP/BenchMark/README.md new file mode 100644 index 00000000..19dc75b4 --- /dev/null +++ b/CPP/BenchMark/README.md @@ -0,0 +1,6 @@ +# google benchmark +this can be enabled by setting the option in the `CPP/CMakeLists.txt` + +```cmake +option(USE_EXTERNAL_GBENCHMARK "Use the googlebenchmark" ON) +``` \ No newline at end of file diff --git a/CPP/BenchMark/StripDuplicateBenchmark.cpp b/CPP/BenchMark/StripDuplicateBenchmark.cpp new file mode 100644 index 00000000..d44b89c8 --- /dev/null +++ b/CPP/BenchMark/StripDuplicateBenchmark.cpp @@ -0,0 +1,85 @@ +#include "benchmark/benchmark.h" +#include "clipper2/clipper.h" +#include "Utils/CommonUtils.h" +#include + +static void CustomArguments(benchmark::internal::Benchmark *b) { + for (int i = 5; i <= 6; ++i) + for (int j = 5; j <= 6; j *= 8) + for (int k = 5; k <= 10; k++) + b->Args({i, j, k}); +} + +template +inline Clipper2Lib::Path +StripDuplicatesCopyVersion(const Clipper2Lib::Path &path, + bool is_closed_path) { + using namespace Clipper2Lib; + if (path.size() == 0) + return Path(); + Path result; + result.reserve(path.size()); + typename Path::const_iterator path_iter = path.cbegin(); + Point first_pt = *path_iter++, last_pt = first_pt; + result.push_back(first_pt); + for (; path_iter != path.cend(); ++path_iter) { + if (*path_iter != last_pt) { + last_pt = *path_iter; + result.push_back(last_pt); + } + } + if (!is_closed_path) + return result; + while (result.size() > 1 && result.back() == first_pt) + result.pop_back(); + return result; +} + +template +inline Clipper2Lib::Paths +StripDuplicatesCopyVersion(const Clipper2Lib::Paths &paths, + bool is_closed_path) { + using namespace Clipper2Lib; + Paths result; + result.reserve(paths.size()); + for (typename Paths::const_iterator paths_citer = paths.cbegin(); + paths_citer != paths.cend(); ++paths_citer) { + result.push_back(StripDuplicatesCopyVersion(*paths_citer, is_closed_path)); + } + return result; +} + +static void BM_StripDuplicatesCopyVersion(benchmark::State &state) { + using namespace Clipper2Lib; + Paths64 op1; + + for (auto _ : state) { + state.PauseTiming(); + int width = state.range(0); + int height = state.range(1); + int count = state.range(2); + op1.push_back(MakeRandomPoly(width, height, count)); + state.ResumeTiming(); + StripDuplicatesCopyVersion(op1, true); + } +} + +static void BM_StripDuplicates(benchmark::State &state) { + using namespace Clipper2Lib; + Paths64 op1; + for (auto _ : state) { + state.PauseTiming(); + int width = state.range(0); + int height = state.range(1); + int count = state.range(2); + op1.push_back(MakeRandomPoly(width, height, count)); + state.ResumeTiming(); + StripDuplicates(op1, true); + } +} + +// Register the function as a benchmark +BENCHMARK(BM_StripDuplicatesCopyVersion)->Apply(CustomArguments); +BENCHMARK(BM_StripDuplicates)->Apply(CustomArguments); +// Run the benchmark +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/CPP/CMakeLists.txt b/CPP/CMakeLists.txt index bf121bf8..b1e37afa 100644 --- a/CPP/CMakeLists.txt +++ b/CPP/CMakeLists.txt @@ -11,6 +11,7 @@ option(CLIPPER2_UTILS "Build utilities" ON) option(CLIPPER2_EXAMPLES "Build examples" ON) option(CLIPPER2_TESTS "Build tests" ON) option(USE_EXTERNAL_GTEST "Use system-wide installed GoogleTest" OFF) +option(USE_EXTERNAL_GBENCHMARK "Use the googlebenchmark" OFF) option(BUILD_SHARED_LIBS "Build shared libs" OFF) set(CLIPPER2_USINGZ "ON" CACHE STRING "Build Clipper2Z, either \"ON\" or \"OFF\" or \"ONLY\"") @@ -83,6 +84,7 @@ if(CLIPPER2_UTILS OR CLIPPER2_TESTS OR CLIPPER2_EXAMPLES) Utils/ClipFileSave.h Utils/Timer.h Utils/Colors.h + Utils/CommonUtils.h ) set(CLIPPER2_UTILS_SRC Utils/clipper.svg.cpp @@ -236,6 +238,10 @@ endif() file(COPY ../Tests/Polygons.txt DESTINATION ${CMAKE_BINARY_DIR} FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ ) endif() +if(USE_EXTERNAL_GBENCHMARK) + add_subdirectory(BenchMark) +endif() + set(CLIPPER2_PCFILES "") foreach(lib ${CLIPPER2_LIBS}) set(pc "${CMAKE_CURRENT_BINARY_DIR}/${lib}.pc") diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index 5d90cd59..4665721f 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -560,21 +560,10 @@ namespace Clipper2Lib template inline void StripDuplicates( Path& path, bool is_closed_path) { - if (path.size() == 0) return; - typename Path::iterator path_iter = path.begin(); - Point first_pt = *path_iter++, last_pt = first_pt; - for (; path_iter != path.end();) - { - if (*path_iter == last_pt) - path_iter = path.erase(path_iter); - else - { - last_pt = *path_iter; - ++path_iter; - } - } - if (is_closed_path) - while (path.size() > 1 && path.back() == first_pt) path.pop_back(); + //https://stackoverflow.com/questions/1041620/whats-the-most-efficient-way-to-erase-duplicates-and-sort-a-vector#:~:text=Let%27s%20compare%20three%20approaches%3A + path.erase(std::unique(path.begin(), path.end()),path.end()); + if (is_closed_path) + while (path.size() > 1 && path.back() == path.front()) path.pop_back(); } template diff --git a/CPP/Utils/CommonUtils.h b/CPP/Utils/CommonUtils.h new file mode 100644 index 00000000..67dbb810 --- /dev/null +++ b/CPP/Utils/CommonUtils.h @@ -0,0 +1,16 @@ +#include +#include "clipper2/clipper.h" +#ifndef __COMMONUTILS_H__ +#define __COMMONUTILS_H__ + +Clipper2Lib::Path64 MakeRandomPoly(int width, int height, unsigned vertCnt) +{ + using namespace Clipper2Lib; + Path64 result; + result.reserve(vertCnt); + for (unsigned i = 0; i < vertCnt; ++i) + result.push_back(Point64(std::rand() % width, std::rand() % height)); + return result; +} + +#endif \ No newline at end of file From c898b4b5260a771412ece105a9e5656ec7fc93ab Mon Sep 17 00:00:00 2001 From: angusj Date: Sat, 8 Apr 2023 22:25:56 +1000 Subject: [PATCH 07/95] Changed default param in SimplifyPaths function **caution** (#485) Further tweaks to ClipperOffset class (#482) clipper.core.h - made PI a conditional define (#478) --- .../include/clipper2/clipper.core.h | 4 +- CPP/Clipper2Lib/include/clipper2/clipper.h | 22 ++-- CPP/Clipper2Lib/src/clipper.offset.cpp | 24 ++-- CPP/Tests/TestOffsets.cpp | 107 +++++++++++++++++- CSharp/Clipper2Lib/Clipper.Offset.cs | 27 ++--- CSharp/Clipper2Lib/Clipper.cs | 66 +++++++++-- Delphi/Clipper2Lib/Clipper.Offset.pas | 24 ++-- Delphi/Clipper2Lib/Clipper.pas | 26 ++--- 8 files changed, 217 insertions(+), 83 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index 4665721f..e4442a68 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 3 April 2023 * +* Date : 8 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library structures and functions * @@ -50,7 +50,9 @@ namespace Clipper2Lib const int non_pair_error_i = 4; // non-fatal const int range_error_i = 64; +#ifndef PI static const double PI = 3.141592653589793238; +#endif static const int64_t MAX_COORD = INT64_MAX >> 2; static const int64_t MIN_COORD = -MAX_COORD; static const int64_t INVALID = INT64_MAX; diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.h b/CPP/Clipper2Lib/include/clipper2/clipper.h index 692da199..ed56c6c2 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 2 April 2023 * +* Date : 8 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -645,7 +645,7 @@ namespace Clipper2Lib { template inline Path SimplifyPath(const Path path, - double epsilon, bool isOpenPath = false) + double epsilon, bool isClosedPath = true) { const size_t len = path.size(), high = len -1; const double epsSqr = Sqr(epsilon); @@ -654,15 +654,15 @@ namespace Clipper2Lib { std::vector flags(len); std::vector distSqr(len); size_t prior = high, curr = 0, start, next, prior2, next2; - if (isOpenPath) + if (isClosedPath) { - distSqr[0] = MAX_DBL; - distSqr[high] = MAX_DBL; + distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]); + distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]); } else { - distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]); - distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]); + distSqr[0] = MAX_DBL; + distSqr[high] = MAX_DBL; } for (size_t i = 1; i < high; ++i) distSqr[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]); @@ -689,7 +689,7 @@ namespace Clipper2Lib { next = GetNext(next, high, flags); next2 = GetNext(next, high, flags); distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); - if (next != high || !isOpenPath) + if (next != high || isClosedPath) distSqr[next] = PerpendicDistFromLineSqrd(path[next], path[curr], path[next2]); curr = next; } @@ -700,7 +700,7 @@ namespace Clipper2Lib { next = GetNext(next, high, flags); prior2 = GetPrior(prior, high, flags); distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); - if (prior != 0 || !isOpenPath) + if (prior != 0 || isClosedPath) distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]); } } @@ -713,12 +713,12 @@ namespace Clipper2Lib { template inline Paths SimplifyPaths(const Paths paths, - double epsilon, bool isOpenPath = false) + double epsilon, bool isClosedPath = true) { Paths result; result.reserve(paths.size()); for (const auto& path : paths) - result.push_back(SimplifyPath(path, epsilon, isOpenPath)); + result.push_back(SimplifyPath(path, epsilon, isClosedPath)); return result; } diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index 0fe0697c..a60cf05b 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 22 March 2023 * +* Date : 8 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -302,13 +302,7 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k) if (sin_a > 1.0) sin_a = 1.0; else if (sin_a < -1.0) sin_a = -1.0; - if (cos_a > 0.99) // almost straight - less than 8 degrees - { - group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_)); - if (cos_a < 0.9998) // greater than 1 degree (#424) - group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); // (#418) - } - else if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) + if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) { // is concave group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_)); @@ -316,21 +310,21 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k) // path reversals are fully cleaned with the trailing clipper group.path.push_back(path[j]); // (#405) group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); - } - else if (join_type_ == JoinType::Round) - DoRound(group, path, j, k, std::atan2(sin_a, cos_a)); + } else if (join_type_ == JoinType::Miter) { // miter unless the angle is so acute the miter would exceeds ML if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a); else DoSquare(group, path, j, k); } - // don't bother squaring angles that deviate < ~20 degrees because - // squaring will be indistinguishable from mitering and just be a lot slower - else if (cos_a > 0.9) + else if (cos_a > 0.9998) + // almost straight - less than 1 degree (#424) DoMiter(group, path, j, k, cos_a); - else + else if (cos_a > 0.99 || join_type_ == JoinType::Square) + //angle less than 8 degrees or squared joins DoSquare(group, path, j, k); + else + DoRound(group, path, j, k, std::atan2(sin_a, cos_a)); k = j; } diff --git a/CPP/Tests/TestOffsets.cpp b/CPP/Tests/TestOffsets.cpp index e197d153..35bed966 100644 --- a/CPP/Tests/TestOffsets.cpp +++ b/CPP/Tests/TestOffsets.cpp @@ -3,7 +3,7 @@ #include "ClipFileLoad.h" #include -TEST(Clipper2Tests, TestOffsets) { +TEST(Clipper2Tests, TestOffsets) { std::ifstream ifs("Offsets.txt"); @@ -39,6 +39,7 @@ TEST(Clipper2Tests, TestOffsets) { } } + Clipper2Lib::Point64 MidPoint(const Clipper2Lib::Point64& p1, const Clipper2Lib::Point64& p2) { Clipper2Lib::Point64 result; @@ -81,4 +82,106 @@ TEST(Clipper2Tests, TestOffsets2) { // see #448 & #456 EXPECT_GE(min_dist + 1, delta - arc_tol); // +1 for rounding errors EXPECT_LE(solution[0].size(), 21); -} \ No newline at end of file +} + +TEST(Clipper2Tests, TestOffsets3) // see #424 +{ + + Clipper2Lib::Paths64 subjects = {{ + {1525311078, 1352369439}, + {1526632284, 1366692987}, + {1519397110, 1367437476}, + {1520246456, 1380177674}, + {1520613458, 1385913385}, + {1517383844, 1386238444}, + {1517771817, 1392099983}, + {1518233190, 1398758441}, + {1518421934, 1401883197}, + {1518694564, 1406612275}, + {1520267428, 1430289121}, + {1520770744, 1438027612}, + {1521148232, 1443438264}, + {1521441833, 1448964260}, + {1521683005, 1452518932}, + {1521819320, 1454374912}, + {1527943004, 1454154711}, + {1527649403, 1448523858}, + {1535901696, 1447989084}, + {1535524209, 1442788147}, + {1538953052, 1442463089}, + {1541553521, 1442242888}, + {1541459149, 1438855987}, + {1538764308, 1439076188}, + {1538575565, 1436832236}, + {1538764308, 1436832236}, + {1536509870, 1405374956}, + {1550497874, 1404347351}, + {1550214758, 1402428457}, + {1543818445, 1402868859}, + {1543734559, 1402124370}, + {1540672717, 1402344571}, + {1540473487, 1399995761}, + {1524996506, 1400981422}, + {1524807762, 1398223667}, + {1530092585, 1397898609}, + {1531675935, 1397783265}, + {1531392819, 1394920653}, + {1529809469, 1395025510}, + {1529348096, 1388880855}, + {1531099218, 1388660654}, + {1530826588, 1385158410}, + {1532955197, 1384938209}, + {1532661596, 1379003269}, + {1532472852, 1376235028}, + {1531277476, 1376350372}, + {1530050642, 1361806623}, + {1599487345, 1352704983}, + {1602758902, 1378489467}, + {1618990858, 1376350372}, + {1615058698, 1344085688}, + {1603230761, 1345700495}, + {1598648484, 1346329641}, + {1598931599, 1348667965}, + {1596698132, 1348993024}, + {1595775386, 1342722540} }}; + + Clipper2Lib::Paths64 solution = InflatePaths(subjects, -209715, + Clipper2Lib::JoinType::Miter, Clipper2Lib::EndType::Polygon); + + EXPECT_LE(solution[0].size() - subjects[0].size(), 1); +} + +TEST(Clipper2Tests, TestOffsets4) // see #482 +{ + Clipper2Lib::Paths64 paths = { { {0, 0}, {20000, 200}, + {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} }; + Clipper2Lib::Paths64 solution = Clipper2Lib::InflatePaths(paths, -5000, + Clipper2Lib::JoinType::Square, Clipper2Lib::EndType::Polygon); + std::cout << solution[0].size() << std::endl; + + EXPECT_EQ(solution[0].size(), 5); + + paths = { { {0, 0}, {20000, 400}, + {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} }; + solution = Clipper2Lib::InflatePaths(paths, -5000, + Clipper2Lib::JoinType::Square, Clipper2Lib::EndType::Polygon); + std::cout << solution[0].size() << std::endl; + + EXPECT_EQ(solution[0].size(), 6); + + paths = { { {0, 0}, {20000, 400}, + {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} }; + solution = Clipper2Lib::InflatePaths(paths, -5000, + Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); + std::cout << solution[0].size() << std::endl; + + EXPECT_EQ(solution[0].size(), 6); + + paths = { { {0, 0}, {20000, 1500}, + {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} }; + solution = Clipper2Lib::InflatePaths(paths, -5000, + Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); + std::cout << solution[0].size() << std::endl; + + EXPECT_GT(solution[0].size(), 6); +} diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index 7cb0ec89..ed6c3ceb 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 2 April 2023 * +* Date : 8 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -433,34 +433,31 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) if (sinA > 1.0) sinA = 1.0; else if (sinA < -1.0) sinA = -1.0; - if (cosA > 0.99) // almost straight - less than 8 degrees - { - group.outPath.Add(GetPerpendic(path[j], _normals[k])); - if (cosA < 0.9998) // greater than 1 degree (#424) - group.outPath.Add(GetPerpendic(path[j], _normals[j])); // (#418) - } - else if (cosA > -0.99 && (sinA * _group_delta < 0)) // is concave + + if (cosA > -0.99 && (sinA * _group_delta < 0)) { + // is concave group.outPath.Add(GetPerpendic(path[j], _normals[k])); // this extra point is the only (simple) way to ensure that // path reversals are fully cleaned with the trailing clipper group.outPath.Add(path[j]); // (#405) group.outPath.Add(GetPerpendic(path[j], _normals[j])); } - else if (_joinType == JoinType.Round) - DoRound(group, path, j, k, Math.Atan2(sinA, cosA)); else if (_joinType == JoinType.Miter) { // miter unless the angle is so acute the miter would exceeds ML if (cosA > _mitLimSqr - 1) DoMiter(group, path, j, k, cosA); else DoSquare(group, path, j, k); } - // don't bother squaring angles that deviate < ~20 degrees because - // squaring will be indistinguishable from mitering and just be a lot slower - else if (cosA > 0.9) - DoMiter(group, path, j, k, cosA); - else + else if (cosA > 0.9998) + // almost straight - less than 1 degree (#424) + DoMiter(group, path, j, k, cosA); + else if (cosA > 0.99 || _joinType == JoinType.Square) + //angle less than 8 degrees or a squared join DoSquare(group, path, j, k); + else + DoRound(group, path, j, k, Math.Atan2(sinA, cosA)); + k = j; } diff --git a/CSharp/Clipper2Lib/Clipper.cs b/CSharp/Clipper2Lib/Clipper.cs index a73f8c46..90c97299 100644 --- a/CSharp/Clipper2Lib/Clipper.cs +++ b/CSharp/Clipper2Lib/Clipper.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 23 March 2023 * +* Date : 8 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module contains simple functions that will likely cover * @@ -859,7 +859,7 @@ private static int GetPrior(int current, int high, ref bool[] flags) } public static Path64 SimplifyPath(Path64 path, - double epsilon, bool isOpenPath = false) + double epsilon, bool isClosedPath = false) { int len = path.Count, high = len - 1; double epsSqr = Sqr(epsilon); @@ -868,16 +868,18 @@ public static Path64 SimplifyPath(Path64 path, bool[] flags = new bool[len]; double[] dsq = new double[len]; int prev = high, curr = 0, start, next, prior2, next2; - if (isOpenPath) + + if (isClosedPath) { - dsq[0] = double.MaxValue; - dsq[high] = double.MaxValue; + dsq[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]); + dsq[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]); } else { - dsq[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]); - dsq[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]); + dsq[0] = double.MaxValue; + dsq[high] = double.MaxValue; } + for (int i = 1; i < high; ++i) dsq[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]); @@ -903,7 +905,7 @@ public static Path64 SimplifyPath(Path64 path, next = GetNext(next, high, ref flags); next2 = GetNext(next, high, ref flags); dsq[curr] = PerpendicDistFromLineSqrd(path[curr], path[prev], path[next]); - if (next != high || !isOpenPath) + if (next != high || isClosedPath) dsq[next] = PerpendicDistFromLineSqrd(path[next], path[curr], path[next2]); curr = next; } @@ -914,7 +916,7 @@ public static Path64 SimplifyPath(Path64 path, next = GetNext(next, high, ref flags); prior2 = GetPrior(prev, high, ref flags); dsq[curr] = PerpendicDistFromLineSqrd(path[curr], path[prev], path[next]); - if (prev != 0 || !isOpenPath) + if (prev != 0 || isClosedPath) dsq[prev] = PerpendicDistFromLineSqrd(path[prev], path[prior2], path[curr]); } } @@ -925,11 +927,11 @@ public static Path64 SimplifyPath(Path64 path, } public static Paths64 SimplifyPaths(Paths64 paths, - double epsilon, bool isOpenPath = false) + double epsilon, bool isClosedPaths = false) { Paths64 result = new Paths64(paths.Count); foreach (Path64 path in paths) - result.Add(SimplifyPath(path, epsilon, isOpenPath)); + result.Add(SimplifyPath(path, epsilon, isClosedPaths)); return result; } @@ -1122,5 +1124,47 @@ public static PathD Ellipse(PointD center, return result; } + private static void ShowPolyPathStructure(PolyPath64 pp, int level) + { + string spaces = new string(' ', level * 2); + string caption = (pp.IsHole ? "Hole " : "Outer "); + if (pp.Count == 0) + { + Console.WriteLine(spaces + caption); + } + else + { + Console.WriteLine(spaces + caption + string.Format("({0})", pp.Count)); + foreach (PolyPath64 child in pp) { ShowPolyPathStructure(child, level + 1); } + } + } + + public static void ShowPolyTreeStructure(PolyTree64 polytree) + { + Console.WriteLine("Polytree Root"); + foreach (PolyPath64 child in polytree) { ShowPolyPathStructure(child, 1); } + } + + private static void ShowPolyPathStructure(PolyPathD pp, int level) + { + string spaces = new string(' ', level * 2); + string caption = (pp.IsHole ? "Hole " : "Outer "); + if (pp.Count == 0) + { + Console.WriteLine(spaces + caption); + } + else + { + Console.WriteLine(spaces + caption + string.Format("({0})", pp.Count)); + foreach (PolyPathD child in pp) { ShowPolyPathStructure(child, level + 1); } + } + } + + public static void ShowPolyTreeStructure(PolyTreeD polytree) + { + Console.WriteLine("Polytree Root"); + foreach (PolyPathD child in polytree) { ShowPolyPathStructure(child, 1); } + } + } // Clipper } // namespace \ No newline at end of file diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index ae21de60..1c0bb2e5 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 2 April 2023 * +* Date : 8 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -820,35 +820,29 @@ procedure TClipperOffset.OffsetPoint(j: Integer; var k: integer); else if (sinA < -1.0) then sinA := -1.0; - if (cosA > 0.99) then // almost straight - less than 8 degrees - begin - AddPoint(GetPerpendic(fInPath[j], fNorms[k], fGroupDelta)); - if (cosA < 0.9998) then // greater than 1 degree (#424) - AddPoint(GetPerpendic(fInPath[j], fNorms[j], fGroupDelta)); // (#418) - end - else if (cosA > -0.99) and (sinA * fGroupDelta < 0) then // is concave + if (cosA > -0.99) and (sinA * fGroupDelta < 0) then begin + // is concave AddPoint(GetPerpendic(fInPath[j], fNorms[k], fGroupDelta)); // this extra point is the only (simple) way to ensure that // path reversals are fully cleaned with the trailing clipper AddPoint(fInPath[j]); // (#405) AddPoint(GetPerpendic(fInPath[j], fNorms[j], fGroupDelta)); end - //else convex of one sort or another - else if (fJoinType = jtRound) then - DoRound(j, k, ArcTan2(sinA, cosA)) else if (fJoinType = jtMiter) then begin // miter unless the angle is so acute the miter would exceeds ML if (cosA > fTmpLimit -1) then DoMiter(j, k, cosA) else DoSquare(j, k); end - // don't bother squaring angles that deviate < ~20 degrees because - // squaring will be indistinguishable from mitering and just be a lot slower - else if (cosA > 0.9) then + else if (cosA > 0.9998) then + // almost straight - less than 1 degree (#424) DoMiter(j, k, cosA) + else if (cosA > 0.99) or (fJoinType = jtSquare) then + //angle less than 8 degrees or squared joins + DoSquare(j, k) else - DoSquare(j, k); + DoRound(j, k, ArcTan2(sinA, cosA)); k := j; end; diff --git a/Delphi/Clipper2Lib/Clipper.pas b/Delphi/Clipper2Lib/Clipper.pas index 0abb2b84..46c95f11 100644 --- a/Delphi/Clipper2Lib/Clipper.pas +++ b/Delphi/Clipper2Lib/Clipper.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 23 March 2023 * +* Date : 8 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -140,10 +140,10 @@ function PointInPolygon(const pt: TPoint64; const polygon: TPath64): TPointInPolygonResult; {$IFDEF INLINE} inline; {$ENDIF} function SimplifyPath(const path: TPath64; - epsilon: double; isOpenPath: Boolean = false): TPath64; + epsilon: double; isClosedPath: Boolean = false): TPath64; {$IFDEF INLINE} inline; {$ENDIF} function SimplifyPaths(const paths: TPaths64; - epsilon: double; isOpenPath: Boolean = false): TPaths64; + epsilon: double; isClosedPaths: Boolean = false): TPaths64; {$IFDEF INLINE} inline; {$ENDIF} implementation @@ -693,7 +693,7 @@ function GetPrior(current, high: integer; var flags: array of Boolean): integer; end; function SimplifyPath(const path: TPath64; - epsilon: double; isOpenPath: Boolean = false): TPath64; + epsilon: double; isClosedPath: Boolean = false): TPath64; var i,j, len, high: integer; curr, prev, start, prev2, next, next2: integer; @@ -710,14 +710,14 @@ function SimplifyPath(const path: TPath64; SetLength(dsq, len); curr := 0; - if (isOpenPath) then - begin - dsq[0] := MaxDouble; - dsq[high] := MaxDouble; - end else + if (isClosedPath) then begin dsq[0] := PerpendicDistFromLineSqrd(path[0], path[high], path[1]); dsq[high] := PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]); + end else + begin + dsq[0] := MaxDouble; + dsq[high] := MaxDouble; end; for i := 1 to high -1 do @@ -745,7 +745,7 @@ function SimplifyPath(const path: TPath64; next2 := GetNext(next, high, flags); dsq[curr] := PerpendicDistFromLineSqrd( path[curr], path[prev], path[next]); - if (next <> high) or not isOpenPath then + if (next <> high) or isClosedPath then dsq[next] := PerpendicDistFromLineSqrd( path[next], path[curr], path[next2]); curr := next; @@ -757,7 +757,7 @@ function SimplifyPath(const path: TPath64; prev2 := GetPrior(prev, high, flags); dsq[curr] := PerpendicDistFromLineSqrd( path[curr], path[prev], path[next]); - if (prev <> 0) or not isOpenPath then + if (prev <> 0) or isClosedPath then dsq[prev] := PerpendicDistFromLineSqrd( path[prev], path[prev2], path[curr]); end; @@ -774,14 +774,14 @@ function SimplifyPath(const path: TPath64; end; function SimplifyPaths(const paths: TPaths64; - epsilon: double; isOpenPath: Boolean = false): TPaths64; + epsilon: double; isClosedPaths: Boolean = false): TPaths64; var i, len: integer; begin len := Length(paths); SetLength(Result, len); for i := 0 to len -1 do - result[i] := SimplifyPath(paths[i], epsilon, isOpenPath); + result[i] := SimplifyPath(paths[i], epsilon, isClosedPaths); end; end. From 1b86265551d61d9be76616a21bdce36ada87cd32 Mon Sep 17 00:00:00 2001 From: Angus Johnson Date: Sat, 8 Apr 2023 22:36:21 +1000 Subject: [PATCH 08/95] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 611e62f6..01c7e589 100644 --- a/README.md +++ b/README.md @@ -45,5 +45,6 @@ Delphi: Compiles with any version of Delphi back to Delphi 7.
### Ports to other languages -**Java**: https://github.com/micycle1/Clipper2-java/ +**Java**: https://github.com/micycle1/Clipper2-java/
+**Kotlin**: https://github.com/Monkey-Maestro/clipper2-kotlin From cda99eddc64ccecbbd8939ddf89113e3d9207309 Mon Sep 17 00:00:00 2001 From: angusj Date: Sat, 22 Apr 2023 18:35:32 +1000 Subject: [PATCH 09/95] Clipper.Engine fixed a minor bug constructing Polytree structure. tidied code used in constructing Polytrees Clipper.RectClip fixed a minor bug --- .../include/clipper2/clipper.engine.h | 4 +- CPP/Clipper2Lib/include/clipper2/clipper.h | 3 +- CPP/Clipper2Lib/src/clipper.engine.cpp | 93 ++++++++-------- CPP/Clipper2Lib/src/clipper.rectclip.cpp | 10 +- CSharp/Clipper2Lib/Clipper.Engine.cs | 86 +++++++-------- CSharp/Clipper2Lib/Clipper.RectClip.cs | 4 +- Delphi/Clipper2Lib/Clipper.Engine.pas | 104 +++++++++--------- Delphi/Clipper2Lib/Clipper.RectClip.pas | 4 +- Delphi/Clipper2Lib/Clipper.pas | 100 ++++++++++++++++- 9 files changed, 245 insertions(+), 163 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h index 30dc6c86..8c4b30d5 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 March 2023 * +* Date : 22 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -256,8 +256,8 @@ namespace Clipper2Lib { bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees); void CleanCollinear(OutRec* outrec); bool CheckBounds(OutRec* outrec); + bool CheckSplitOwner(OutRec* outrec); void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath); - void DeepCheckOwners(OutRec* outrec, PolyPath* polypath); #ifdef USINGZ ZCallback64 zCallback_ = nullptr; void SetZ(const Active& e1, const Active& e2, Point64& pt); diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.h b/CPP/Clipper2Lib/include/clipper2/clipper.h index ed56c6c2..f406eaef 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 8 April 2023 * +* Date : 21 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -370,6 +370,7 @@ namespace Clipper2Lib { inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp) { + os << std::endl << "Polytree root" << std::endl; PolyPath64List::const_iterator it = pp.begin(); for (; it < pp.end() - 1; ++it) details::OutlinePolyPath64(os, **it, " ", false); diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index 2d61b8aa..3337a4ec 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 19 March 2023 * +* Date : 22 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2127,7 +2127,11 @@ namespace Clipper2Lib { else if (Path1InsidePath2(or1->pts, or2->pts)) SetOwner(or1, or2); else + { + if (!or1->splits) or1->splits = new OutRecList(); + or1->splits->push_back(or2); //(#498) or2->owner = or1; + } } else or2->owner = or1; @@ -2638,10 +2642,10 @@ namespace Clipper2Lib { const Point64& pt, bool check_curr_x) { Active* prev = e.prev_in_ael; - if (IsOpen(e) || !IsHotEdge(e) || !prev || - IsOpen(*prev) || !IsHotEdge(*prev) || - pt.y < e.top.y + 2 || pt.y < prev->top.y + 2) // avoid trivial joins - return; + if (IsOpen(e) || !IsHotEdge(e) || !prev || + IsOpen(*prev) || !IsHotEdge(*prev)) return; + if ((pt.y < e.top.y + 2 || pt.y < prev->top.y + 2) && + ((e.bot.y > pt.y) || (prev->bot.y > pt.y))) return; // avoid trivial joins if (check_curr_x) { @@ -2664,10 +2668,10 @@ namespace Clipper2Lib { const Point64& pt, bool check_curr_x) { Active* next = e.next_in_ael; - if (IsOpen(e) || !IsHotEdge(e) || - !next || IsOpen(*next) || !IsHotEdge(*next) || - pt.y < e.top.y +2 || pt.y < next->top.y +2) // avoids trivial joins - return; + if (IsOpen(e) || !IsHotEdge(e) || + !next || IsOpen(*next) || !IsHotEdge(*next)) return; + if ((pt.y < e.top.y +2 || pt.y < next->top.y +2) && + ((e.bot.y > pt.y) || (next->bot.y > pt.y))) return; // avoid trivial joins if (check_curr_x) { @@ -2682,6 +2686,7 @@ namespace Clipper2Lib { JoinOutrecPaths(e, *next); else JoinOutrecPaths(*next, e); + e.join_with = JoinWith::Right; next->join_with = JoinWith::Left; } @@ -2758,6 +2763,23 @@ namespace Clipper2Lib { return true; } + bool ClipperBase::CheckSplitOwner(OutRec* outrec) + { + for (auto s : *outrec->owner->splits) + { + OutRec* split = GetRealOutRec(s); + if (split && split != outrec && + split != outrec->owner && CheckBounds(split) && + split->bounds.Contains(outrec->bounds) && + Path1InsidePath2(outrec->pts, split->pts)) + { + outrec->owner = split; //found in split + return true; + } + } + return false; + } + void ClipperBase::RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath) { // pre-condition: outrec will have valid bounds @@ -2765,52 +2787,25 @@ namespace Clipper2Lib { if (outrec->polypath || outrec->bounds.IsEmpty()) return; - while (outrec->owner && - (!outrec->owner->pts || !CheckBounds(outrec->owner))) - outrec->owner = outrec->owner->owner; - - if (outrec->owner && !outrec->owner->polypath) - RecursiveCheckOwners(outrec->owner, polypath); - while (outrec->owner) - if (outrec->owner->bounds.Contains(outrec->bounds) && - Path1InsidePath2(outrec->pts, outrec->owner->pts)) - break; // found - owner contain outrec! - else - outrec->owner = outrec->owner->owner; + { + if (outrec->owner->splits && CheckSplitOwner(outrec)) break; + if (outrec->owner->pts && CheckBounds(outrec->owner) && + outrec->owner->bounds.Contains(outrec->bounds) && + Path1InsidePath2(outrec->pts, outrec->owner->pts)) break; + outrec->owner = outrec->owner->owner; + } if (outrec->owner) + { + if (!outrec->owner->polypath) + RecursiveCheckOwners(outrec->owner, polypath); outrec->polypath = outrec->owner->polypath->AddChild(outrec->path); + } else outrec->polypath = polypath->AddChild(outrec->path); } - void ClipperBase::DeepCheckOwners(OutRec* outrec, PolyPath* polypath) - { - RecursiveCheckOwners(outrec, polypath); - - while (outrec->owner && outrec->owner->splits) - { - OutRec* split = nullptr; - for (auto s : *outrec->owner->splits) - { - split = GetRealOutRec(s); - if (split && split != outrec && - split != outrec->owner && CheckBounds(split) && - split->bounds.Contains(outrec->bounds) && - Path1InsidePath2(outrec->pts, split->pts)) - { - RecursiveCheckOwners(split, polypath); - outrec->owner = split; //found in split - break; // inner 'for' loop - } - else - split = nullptr; - } - if (!split) break; - } - } - void Clipper64::BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen) { solutionClosed.resize(0); @@ -2869,7 +2864,7 @@ namespace Clipper2Lib { } if (CheckBounds(outrec)) - DeepCheckOwners(outrec, &polytree); + RecursiveCheckOwners(outrec, &polytree); } } @@ -2972,7 +2967,7 @@ namespace Clipper2Lib { } if (CheckBounds(outrec)) - DeepCheckOwners(outrec, &polytree); + RecursiveCheckOwners(outrec, &polytree); } } diff --git a/CPP/Clipper2Lib/src/clipper.rectclip.cpp b/CPP/Clipper2Lib/src/clipper.rectclip.cpp index 959972b4..54e750f8 100644 --- a/CPP/Clipper2Lib/src/clipper.rectclip.cpp +++ b/CPP/Clipper2Lib/src/clipper.rectclip.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 14 February 2023 * +* Date : 22 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -619,7 +619,7 @@ namespace Clipper2Lib { p1 = cw[i]; if (!p1 || p1->next == p1->prev) { - cw[i++]->edge = nullptr; + cw[i++] = nullptr; j = 0; continue; } @@ -819,8 +819,8 @@ namespace Clipper2Lib { Paths64 result; if (rect_.IsEmpty()) return result; - for (const auto& path : paths) - { + for (const Path64& path : paths) + { if (path.size() < 3) continue; path_bounds_ = GetBounds(path); if (!rect_.Intersects(path_bounds_)) @@ -867,9 +867,7 @@ namespace Clipper2Lib { for (const auto& path : paths) { - if (path.size() < 2) continue; Rect64 pathrec = GetBounds(path); - if (!rect_.Intersects(pathrec)) continue; ExecuteInternal(path); diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 26c22498..a393d138 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 28 March 2023 * +* Date : 22 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2351,9 +2351,10 @@ private void CheckJoinLeft(Active e, { Active? prev = e.prevInAEL; if (prev == null || IsOpen(e) || IsOpen(prev) || - !IsHotEdge(e) || !IsHotEdge(prev) || - pt.Y < e.top.Y + 2 || pt.Y < prev.top.Y + 2) return; - + !IsHotEdge(e) || !IsHotEdge(prev)) return; + if ((pt.Y < e.top.Y + 2 || pt.Y < prev.top.Y + 2) && // avoid trivial joins + ((e.bot.Y > pt.Y) || (prev.bot.Y > pt.Y))) return; // (#490) + if (checkCurrX) { if (Clipper.PerpendicDistFromLineSqrd(pt, prev.bot, prev.top) > 0.25) return; @@ -2377,9 +2378,9 @@ private void CheckJoinRight(Active e, { Active? next = e.nextInAEL; if (IsOpen(e) || !IsHotEdge(e) || IsJoined(e) || - next == null || IsOpen(next) || !IsHotEdge(next) || - pt.Y < e.top.Y + 2 || pt.Y < next.top.Y + 2) // avoids trivial joins - return; + next == null || IsOpen(next) || !IsHotEdge(next)) return; + if ((pt.Y < e.top.Y + 2 || pt.Y < next.top.Y + 2) && // avoid trivial joins + ((e.bot.Y > pt.Y) || (next.bot.Y > pt.Y))) return; // (#490) if (checkCurrX) { @@ -2668,7 +2669,11 @@ private void ProcessHorzJoins() else if (Path1InsidePath2(or1.pts, or2.pts)) SetOwner(or1, or2); else + { + or1.splits ??= new List(); + or1.splits.Add(or2.idx); // (#498) or2.owner = or1; + } } else or2.owner = or1; @@ -2954,6 +2959,22 @@ private bool CheckBounds(OutRec outrec) return true; } + private bool CheckSplitOwner(OutRec outrec) + { + foreach (int i in outrec.owner!.splits!) + { + OutRec? split = GetRealOutRec(_outrecList[i]); + if (split != null && split != outrec && + split != outrec.owner && CheckBounds(split) && + split.bounds.Contains(outrec.bounds) && + Path1InsidePath2(outrec.pts!, split.pts!)) + { + outrec.owner = split; //found in split + return true; + } + } + return false; + } private void RecursiveCheckOwners(OutRec outrec, PolyPathBase polypath) { // pre-condition: outrec will have valid bounds @@ -2961,49 +2982,22 @@ private void RecursiveCheckOwners(OutRec outrec, PolyPathBase polypath) if (outrec.polypath != null || outrec.bounds.IsEmpty()) return; - while (outrec.owner != null && - (outrec.owner.pts == null || !CheckBounds(outrec.owner))) - outrec.owner = outrec.owner.owner; - - if (outrec.owner != null && outrec.owner.polypath == null) - RecursiveCheckOwners(outrec.owner, polypath); - while (outrec.owner != null) - if (outrec.owner.bounds.Contains(outrec.bounds) && - Path1InsidePath2(outrec.pts!, outrec.owner.pts!)) - break; // found - owner contain outrec! - else - outrec.owner = outrec.owner.owner; + { + if (outrec.owner.splits != null && CheckSplitOwner(outrec)) break; + if (outrec.owner.pts != null && CheckBounds(outrec.owner) && + Path1InsidePath2(outrec.pts!, outrec.owner.pts!)) break; + outrec.owner = outrec.owner.owner; + } if (outrec.owner != null) - outrec.polypath = outrec.owner.polypath!.AddChild(outrec.path); - else - outrec.polypath = polypath.AddChild(outrec.path); - } - - private void DeepCheckOwners(OutRec outrec, PolyPathBase polypath) - { - RecursiveCheckOwners(outrec, polypath); - - while (outrec.owner != null && outrec.owner.splits != null) { - OutRec? split = null; - foreach (int i in outrec.owner.splits) - { - split = GetRealOutRec(_outrecList[i]); - if (split != null && split != outrec && - split != outrec.owner && CheckBounds(split) && - split.bounds.Contains(outrec.bounds) && - Path1InsidePath2(outrec.pts!, split.pts!)) - { - RecursiveCheckOwners(split, polypath); - outrec.owner = split; //found in split - break; // inner 'for' loop - } - else split = null; - } - if (split == null) break; + if (outrec.owner.polypath == null) + RecursiveCheckOwners(outrec.owner, polypath); + outrec.polypath = outrec.owner.polypath!.AddChild(outrec.path); } + else + outrec.polypath = polypath.AddChild(outrec.path); } protected void BuildTree(PolyPathBase polytree, Paths64 solutionOpen) @@ -3030,7 +3024,7 @@ protected void BuildTree(PolyPathBase polytree, Paths64 solutionOpen) continue; } if (CheckBounds(outrec)) - DeepCheckOwners(outrec, polytree); + RecursiveCheckOwners(outrec, polytree); } } diff --git a/CSharp/Clipper2Lib/Clipper.RectClip.cs b/CSharp/Clipper2Lib/Clipper.RectClip.cs index aed89823..6e6354b4 100644 --- a/CSharp/Clipper2Lib/Clipper.RectClip.cs +++ b/CSharp/Clipper2Lib/Clipper.RectClip.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 February 2023 * +* Date : 22 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -720,7 +720,7 @@ private void TidyEdgePair(int idx, List cw, List ccw) p1 = cw[i]; if (p1 == null || p1.next == p1.prev) { - cw[i++]!.edge = null; + cw[i++] = null; j = 0; continue; } diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index 1c0da73e..e07e2fde 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 26 March 2023 * +* Date : 22 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -257,8 +257,8 @@ TClipperBase = class procedure DoSplitOp(outrec: POutRec; splitOp: POutPt); procedure FixSelfIntersects(outrec: POutRec); function CheckBounds(outrec: POutRec): Boolean; + function CheckSplitOwner(outrec: POutRec): Boolean; procedure RecursiveCheckOwners(outrec: POutRec; polytree: TPolyPathBase); - procedure DeepCheckOwners(outrec: POutRec; polytree: TPolyPathBase); protected FUsingPolytree : Boolean; procedure AddPath(const path: TPath64; @@ -1958,7 +1958,7 @@ procedure TClipperBase.DoSplitOp(outrec: POutRec; splitOp: POutPt); // the area of the triangle containing splitOp & splitOp.next. // So the only way for these areas to have the same sign is if // the split triangle is larger than the path containing prevOp or - // if there's more than one self=intersection. + // if there's more than one self-intersection. area2 := AreaTriangle(ip, splitOp.pt, splitOp.next.pt); absArea2 := abs(area2); @@ -2153,8 +2153,10 @@ procedure TClipperBase.CheckJoinLeft(e: PActive; begin prev := e.prevInAEL; if IsOpen(e) or not IsHotEdge(e) or not Assigned(prev) or - IsOpen(prev) or not IsHotEdge(prev) or - (pt.Y < e.top.Y +2) or (pt.Y < prev.top.Y +2) then Exit; + IsOpen(prev) or not IsHotEdge(prev) then Exit; + if ((pt.Y < e.top.Y +2) or (pt.Y < prev.top.Y +2)) and + ((e.bot.Y > pt.Y) or (prev.bot.Y > pt.Y)) then Exit; + if checkCurrX then begin if DistanceFromLineSqrd(pt, prev.bot, prev.top) > 0.25 then Exit @@ -2180,8 +2182,9 @@ procedure TClipperBase.CheckJoinRight(e: PActive; begin next := e.nextInAEL; if IsOpen(e) or not IsHotEdge(e) or not Assigned(next) or - IsOpen(next) or not IsHotEdge(next) or - (pt.Y < e.top.Y +2) or (pt.Y < next.top.Y +2) then Exit; + IsOpen(next) or not IsHotEdge(next) then Exit; + if ((pt.Y < e.top.Y +2) or (pt.Y < next.top.Y +2)) and + ((e.bot.Y > pt.Y) or (next.bot.Y > pt.Y)) then Exit; if (checkCurrX) then begin @@ -2907,7 +2910,7 @@ procedure TClipperBase.ProcessHorzJoins; var i: integer; or1, or2: POutRec; - op1b, op2b: POutPt; + op, op1b, op2b: POutPt; begin for i := 0 to FHorzJoinList.Count -1 do with PHorzJoin(FHorzJoinList[i])^ do @@ -2942,7 +2945,10 @@ procedure TClipperBase.ProcessHorzJoins; else if Path1InsidePath2(or1.pts, or2.pts) then SetOwner(or1, or2) else + begin + AddSplit(or1, or2); //(#498) or2.owner := or1; + end; end else or2.owner := or1; end else @@ -3607,6 +3613,28 @@ function TClipperBase.CheckBounds(outrec: POutRec): Boolean; end; //------------------------------------------------------------------------------ +function TClipperBase.CheckSplitOwner(outrec: POutRec): Boolean; +var + i : integer; + split : POutrec; +begin + for i := 0 to High(outrec.owner.splits) do + begin + split := GetRealOutRec(outrec.owner.splits[i]); + if Assigned(split) and (split <> outrec) and + (split <> outrec.owner) and CheckBounds(split) and + (split.bounds.Contains(outrec.bounds) and + Path1InsidePath2(outrec.pts, split.pts)) then + begin + outrec.owner := split; + Result := True; + Exit; + end; + end; + Result := false; +end; +//------------------------------------------------------------------------------ + procedure TClipperBase.RecursiveCheckOwners(outrec: POutRec; polytree: TPolyPathBase); begin // pre-condition: outrec will have valid bounds @@ -3616,54 +3644,24 @@ procedure TClipperBase.RecursiveCheckOwners(outrec: POutRec; polytree: TPolyPath outrec.bounds.IsEmpty then Exit; - while Assigned(outrec.owner) and - (not Assigned(outrec.owner.pts) or - not CheckBounds(outrec.owner)) do - outrec.owner := outrec.owner.owner; - - if Assigned(outrec.owner) and - not Assigned(outrec.owner.polypath) then - RecursiveCheckOwners(outrec.owner, polytree); - while Assigned(outrec.owner) do - if (outrec.owner.bounds.Contains(outrec.bounds) and - Path1InsidePath2(outrec.pts, outrec.owner.pts)) then - break // found - owner contain outrec! - else - outrec.owner := outrec.owner.owner; + begin + if Assigned(outrec.owner.splits) and + CheckSplitOwner(outrec) then Break; + if Assigned(outrec.owner.pts) and + CheckBounds(outrec.owner) and + (outrec.owner.bounds.Contains(outrec.bounds) and + Path1InsidePath2(outrec.pts, outrec.owner.pts)) then break; + outrec.owner := outrec.owner.owner; + end; if Assigned(outrec.owner) then - outrec.polypath := outrec.owner.polypath.AddChild(outrec.path) else - outrec.polypath := polytree.AddChild(outrec.path); -end; -//------------------------------------------------------------------------------ - -procedure TClipperBase.DeepCheckOwners(outrec: POutRec; polytree: TPolyPathBase); -var - i : integer; - split : POutrec; -begin - RecursiveCheckOwners(outrec, polytree); - - while Assigned(outrec.owner) and Assigned(outrec.owner.splits) do begin - split := nil; - for i := 0 to High(outrec.owner.splits) do - begin - split := GetRealOutRec(outrec.owner.splits[i]); - if Assigned(split) and (split <> outrec) and - (split <> outrec.owner) and CheckBounds(split) and - (split.bounds.Contains(outrec.bounds) and - Path1InsidePath2(outrec.pts, split.pts)) then - begin - RecursiveCheckOwners(split, polytree); - outrec.owner := split; //found in split - break; // inner 'for' loop - end else - split := nil; - end; - if not Assigned(split) then break; - end; + if not Assigned(outrec.owner.polypath) then + RecursiveCheckOwners(outrec.owner, polytree); + outrec.polypath := outrec.owner.polypath.AddChild(outrec.path) + end else + outrec.polypath := polytree.AddChild(outrec.path); end; //------------------------------------------------------------------------------ @@ -3704,7 +3702,7 @@ function TClipperBase.BuildTree(polytree: TPolyPathBase; end; if CheckBounds(outrec) then - DeepCheckOwners(outrec, polytree); + RecursiveCheckOwners(outrec, polytree); end; setLength(openPaths, cntOpen); Result := FSucceeded; diff --git a/Delphi/Clipper2Lib/Clipper.RectClip.pas b/Delphi/Clipper2Lib/Clipper.RectClip.pas index d332b6ae..bd581d6d 100644 --- a/Delphi/Clipper2Lib/Clipper.RectClip.pas +++ b/Delphi/Clipper2Lib/Clipper.RectClip.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 13 February 2023 * +* Date : 22 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -862,7 +862,7 @@ procedure TRectClip.TidyEdgePair(idx: integer; var cw, ccw: TOutPtArray); p1 := cw[i]; if not Assigned(p1) or (p1.next = p1.prev) then begin - cw[i].edge := nil; + cw[i] := nil; inc(i); j := 0; Continue; diff --git a/Delphi/Clipper2Lib/Clipper.pas b/Delphi/Clipper2Lib/Clipper.pas index 46c95f11..626d710e 100644 --- a/Delphi/Clipper2Lib/Clipper.pas +++ b/Delphi/Clipper2Lib/Clipper.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 8 April 2023 * +* Date : 21 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -14,7 +14,7 @@ interface {$I Clipper.inc} uses - Math, SysUtils, + Math, SysUtils, Classes, Clipper.Core, Clipper.Engine, Clipper.Offset, Clipper.RectClip; // Redeclare here a number of structures defined in @@ -128,6 +128,10 @@ function MinkowskiSum(const pattern, path: TPathD; function PolyTreeToPaths64(PolyTree: TPolyTree64): TPaths64; function PolyTreeToPathsD(PolyTree: TPolyTreeD): TPathsD; +//ShowPolyTreeStructure: only useful when debugging +procedure ShowPolyTreeStructure(polytree: TPolyTree64; strings: TStrings); overload; +procedure ShowPolyTreeStructure(polytree: TPolyTreeD; strings: TStrings); overload; + function MakePath(const ints: array of Int64): TPath64; overload; function MakePathD(const dbls: array of double): TPathD; overload; @@ -154,6 +158,38 @@ implementation //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ +{$IFDEF USINGZ} +function MakePath(const ints: array of Int64): TPath64; +var + i, len: integer; +begin + len := length(ints) div 3; + SetLength(Result, len); + for i := 0 to len -1 do + begin + Result[i].X := ints[i*3]; + Result[i].Y := ints[i*3 +1]; + Result[i].z := ints[i*3 +2]; + end; +end; +//------------------------------------------------------------------------------ + +function MakePathD(const dbls: array of double): TPathD; overload; +var + i, len: integer; +begin + len := length(dbls) div 3; + SetLength(Result, len); + for i := 0 to len -1 do + begin + Result[i].X := dbls[i*3]; + Result[i].Y := dbls[i*3 +1]; + Result[i].Z := Round(dbls[i*3 +2]); + end; +end; +//------------------------------------------------------------------------------ +{$ELSE} + function MakePath(const ints: array of Int64): TPath64; var i, len: integer; @@ -181,6 +217,7 @@ function MakePathD(const dbls: array of double): TPathD; overload; end; end; //------------------------------------------------------------------------------ +{$ENDIF} procedure AddPolyNodeToPaths(Poly: TPolyPath64; var Paths: TPaths64); var @@ -583,6 +620,65 @@ function MinkowskiSum(const pattern, path: TPathD; end; //------------------------------------------------------------------------------ +procedure ShowPolyPathStructure64(pp: TPolyPath64; level: integer; strings: TStrings); +var + i: integer; + spaces, caption: string; +begin + spaces := StringOfChar(' ', level * 2); + if pp.IsHole then + caption := 'Hole' else + caption := 'Outer'; + if (pp.Count > 0) then + begin + strings.Add(Format('%s%s (%d)',[spaces, caption, pp.Count])); + for i := 0 to pp.Count -1 do + ShowPolyPathStructure64(pp.child[i], level + 1, strings); + end else + strings.Add(spaces + caption); +end; +//------------------------------------------------------------------------------ + +procedure ShowPolyTreeStructure(polytree: TPolyTree64; strings: TStrings); +var + i: integer; +begin + strings.Add('Polytree Root'); + for i := 0 to polytree.Count -1 do + ShowPolyPathStructure64(polytree[i], 1, strings); +end; +//------------------------------------------------------------------------------ + +procedure ShowPolyPathStructureD(pp: TPolyPathD; level: integer; strings: TStrings); +var + i: integer; + spaces, caption: string; +begin + spaces := StringOfChar(' ', level * 2); + if pp.IsHole then + caption := 'Hole ' else + caption := 'Outer '; + if (pp.Count > 0) then + begin + strings.Add(Format('%s%s (%d)',[spaces + caption, pp.Count])); + for i := 0 to pp.Count -1 do + ShowPolyPathStructureD(pp.child[i], level + 1, strings); + end else + strings.Add(spaces + caption); +end; +//------------------------------------------------------------------------------ + +procedure ShowPolyTreeStructure(polytree: TPolyTreeD; strings: TStrings); +var + i: integer; +begin + strings.Add('Polytree Root'); + for i := 0 to polytree.Count -1 do + ShowPolyPathStructureD(polytree[i], 1, strings); +end; +//------------------------------------------------------------------------------ + + function TrimCollinear(const p: TPath64; isOpenPath: Boolean = false): TPath64; var i,j, len: integer; From d18faa9a262ea27fcf52e0454611e9a3f8d74f30 Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 23 Apr 2023 12:57:48 +1000 Subject: [PATCH 10/95] fixed signifcant clipping bug (#500) --- CPP/Clipper2Lib/src/clipper.engine.cpp | 4 ++-- CSharp/Clipper2Lib/Clipper.Engine.cs | 4 ++-- Delphi/Clipper2Lib/Clipper.Engine.pas | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index 3337a4ec..5172bff2 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 22 April 2023 * +* Date : 23 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -1536,7 +1536,7 @@ namespace Clipper2Lib { InsertScanline(e->top.y); CheckJoinLeft(*e, e->bot); - CheckJoinRight(*e, e->bot); + CheckJoinRight(*e, e->bot, true); // (#500) } Active* FindEdgeWithMatchingLocMin(Active* e) diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index a393d138..19a6b3a1 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 22 April 2023 * +* Date : 23 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -1480,7 +1480,7 @@ private void UpdateEdgeIntoAEL(Active ae) InsertScanline(ae.top.Y); CheckJoinLeft(ae, ae.bot); - CheckJoinRight(ae, ae.bot); + CheckJoinRight(ae, ae.bot, true); // (#500) } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index e07e2fde..84fa1477 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 22 April 2023 * +* Date : 23 April 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2155,7 +2155,7 @@ procedure TClipperBase.CheckJoinLeft(e: PActive; if IsOpen(e) or not IsHotEdge(e) or not Assigned(prev) or IsOpen(prev) or not IsHotEdge(prev) then Exit; if ((pt.Y < e.top.Y +2) or (pt.Y < prev.top.Y +2)) and - ((e.bot.Y > pt.Y) or (prev.bot.Y > pt.Y)) then Exit; + ((e.bot.Y > pt.Y) or (prev.bot.Y > pt.Y)) then Exit; // (#490) if checkCurrX then begin @@ -2182,9 +2182,9 @@ procedure TClipperBase.CheckJoinRight(e: PActive; begin next := e.nextInAEL; if IsOpen(e) or not IsHotEdge(e) or not Assigned(next) or - IsOpen(next) or not IsHotEdge(next) then Exit; + not IsHotEdge(next) or IsOpen(next) then Exit; if ((pt.Y < e.top.Y +2) or (pt.Y < next.top.Y +2)) and - ((e.bot.Y > pt.Y) or (next.bot.Y > pt.Y)) then Exit; + ((e.bot.Y > pt.Y) or (next.bot.Y > pt.Y)) then Exit; // (#490) if (checkCurrX) then begin @@ -2272,7 +2272,7 @@ procedure TClipperBase.UpdateEdgeIntoAEL(var e: PActive); InsertScanLine(e.top.Y); CheckJoinLeft(e, e.bot); - CheckJoinRight(e, e.bot); + CheckJoinRight(e, e.bot, true); // (#500) end; //------------------------------------------------------------------------------ From 95edbf69508c2b00f8bc6b4792708e4fae1ece66 Mon Sep 17 00:00:00 2001 From: angusj Date: Mon, 1 May 2023 19:12:22 +1000 Subject: [PATCH 11/95] Added ReuseableDataContainer64 class (Discuss. #491) Fixed a minor bug in Clipper.Engine.cs (#510) Fixed minor bugs in Clipper.Core.cs --- .../include/clipper2/clipper.engine.h | 17 +- CPP/Clipper2Lib/src/clipper.engine.cpp | 263 +++++++++++------- CSharp/Clipper2Lib/Clipper.Core.cs | 24 +- CSharp/Clipper2Lib/Clipper.Engine.cs | 250 ++++++++++------- Delphi/Clipper2Lib/Clipper.Engine.pas | 109 +++++++- Delphi/Clipper2Lib/Clipper.Offset.pas | 5 - 6 files changed, 439 insertions(+), 229 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h index 8c4b30d5..840d1f50 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 22 April 2023 * +* Date : 1 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -179,6 +179,20 @@ namespace Clipper2Lib { typedef std::vector LocalMinimaList; typedef std::vector IntersectNodeList; + // ReuseableDataContainer64 ------------------------------------------------ + + class ReuseableDataContainer64 { + private: + friend class ClipperBase; + LocalMinimaList minima_list_; + std::vector vertex_lists_; + void AddLocMin(Vertex& vert, PathType polytype, bool is_open); + public: + virtual ~ReuseableDataContainer64(); + void Clear(); + void AddPaths(const Paths64& paths, PathType polytype, bool is_open); + }; + // ClipperBase ------------------------------------------------------------- class ClipperBase { @@ -271,6 +285,7 @@ namespace Clipper2Lib { bool PreserveCollinear = true; bool ReverseSolution = false; void Clear(); + void AddReuseableData(const ReuseableDataContainer64& reuseable_data); #ifdef USINGZ int64_t DefaultZ = 0; #endif diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index 5172bff2..141c47eb 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 23 April 2023 * +* Date : 1 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -485,107 +485,25 @@ namespace Clipper2Lib { } //------------------------------------------------------------------------------ - // ClipperBase methods ... //------------------------------------------------------------------------------ - ClipperBase::~ClipperBase() - { - Clear(); - } - - void ClipperBase::DeleteEdges(Active*& e) - { - while (e) - { - Active* e2 = e; - e = e->next_in_ael; - delete e2; - } - } - - void ClipperBase::CleanUp() - { - DeleteEdges(actives_); - scanline_list_ = std::priority_queue(); - intersect_nodes_.clear(); - DisposeAllOutRecs(); - horz_seg_list_.clear(); - horz_join_list_.clear(); - } - - - void ClipperBase::Clear() - { - CleanUp(); - DisposeVerticesAndLocalMinima(); - current_locmin_iter_ = minima_list_.begin(); - minima_list_sorted_ = false; - has_open_paths_ = false; - } - - - void ClipperBase::Reset() + void AddLocMin(LocalMinimaList& list, + Vertex& vert, PathType polytype, bool is_open) { - if (!minima_list_sorted_) - { - std::sort(minima_list_.begin(), minima_list_.end(), LocMinSorter()); - minima_list_sorted_ = true; - } - LocalMinimaList::const_reverse_iterator i; - for (i = minima_list_.rbegin(); i != minima_list_.rend(); ++i) - InsertScanline((*i)->vertex->pt.y); - - current_locmin_iter_ = minima_list_.begin(); - actives_ = nullptr; - sel_ = nullptr; - succeeded_ = true; - } - - -#ifdef USINGZ - void ClipperBase::SetZ(const Active& e1, const Active& e2, Point64& ip) - { - if (!zCallback_) return; - // prioritize subject over clip vertices by passing - // subject vertices before clip vertices in the callback - if (GetPolyType(e1) == PathType::Subject) - { - if (ip == e1.bot) ip.z = e1.bot.z; - else if (ip == e1.top) ip.z = e1.top.z; - else if (ip == e2.bot) ip.z = e2.bot.z; - else if (ip == e2.top) ip.z = e2.top.z; - else ip.z = DefaultZ; - zCallback_(e1.bot, e1.top, e2.bot, e2.top, ip); - } - else - { - if (ip == e2.bot) ip.z = e2.bot.z; - else if (ip == e2.top) ip.z = e2.top.z; - else if (ip == e1.bot) ip.z = e1.bot.z; - else if (ip == e1.top) ip.z = e1.top.z; - else ip.z = DefaultZ; - zCallback_(e2.bot, e2.top, e1.bot, e1.top, ip); - } - } -#endif + //make sure the vertex is added only once ... + if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::None) return; - void ClipperBase::AddPath(const Path64& path, PathType polytype, bool is_open) - { - Paths64 tmp; - tmp.push_back(path); - AddPaths(tmp, polytype, is_open); + vert.flags = (vert.flags | VertexFlags::LocalMin); + list.push_back(std::make_unique (&vert, polytype, is_open)); } - - void ClipperBase::AddPaths(const Paths64& paths, PathType polytype, bool is_open) + void AddPaths_(const Paths64& paths, PathType polytype, bool is_open, + std::vector& vertexLists, LocalMinimaList& locMinList) { - if (is_open) has_open_paths_ = true; - minima_list_sorted_ = false; - const auto total_vertex_count = - std::accumulate(paths.begin(), paths.end(), 0, - [](const auto& a, const Path64& path) - {return a + static_cast(path.size());}); + std::accumulate(paths.begin(), paths.end(), 0, + [](const auto& a, const Path64& path) + {return a + static_cast(path.size()); }); if (total_vertex_count == 0) return; Vertex* vertices = new Vertex[total_vertex_count], * v = vertices; @@ -631,7 +549,7 @@ namespace Clipper2Lib { if (going_up) { v0->flags = VertexFlags::OpenStart; - AddLocMin(*v0, polytype, true); + AddLocMin(locMinList , *v0, polytype, true); } else v0->flags = VertexFlags::OpenStart | VertexFlags::LocalMax; @@ -659,7 +577,7 @@ namespace Clipper2Lib { else if (curr_v->pt.y < prev_v->pt.y && !going_up) { going_up = true; - AddLocMin(*prev_v, polytype, is_open); + AddLocMin(locMinList, *prev_v, polytype, is_open); } prev_v = curr_v; curr_v = curr_v->next; @@ -671,18 +589,163 @@ namespace Clipper2Lib { if (going_up) prev_v->flags = prev_v->flags | VertexFlags::LocalMax; else - AddLocMin(*prev_v, polytype, is_open); + AddLocMin(locMinList, *prev_v, polytype, is_open); } else if (going_up != going_up0) { - if (going_up0) AddLocMin(*prev_v, polytype, false); + if (going_up0) AddLocMin(locMinList, *prev_v, polytype, false); else prev_v->flags = prev_v->flags | VertexFlags::LocalMax; } } // end processing current path - vertex_lists_.emplace_back(vertices); - } // end AddPaths + vertexLists.emplace_back(vertices); + } + + + + //------------------------------------------------------------------------------ + // ReuseableDataContainer64 methods ... + //------------------------------------------------------------------------------ + + void ReuseableDataContainer64::AddLocMin(Vertex& vert, PathType polytype, bool is_open) + { + //make sure the vertex is added only once ... + if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::None) return; + + vert.flags = (vert.flags | VertexFlags::LocalMin); + minima_list_.push_back(std::make_unique (&vert, polytype, is_open)); + } + + void ReuseableDataContainer64::AddPaths(const Paths64& paths, + PathType polytype, bool is_open) + { + AddPaths_(paths, polytype, is_open, vertex_lists_, minima_list_); + } + + ReuseableDataContainer64::~ReuseableDataContainer64() + { + Clear(); + } + + void ReuseableDataContainer64::Clear() + { + minima_list_.clear(); + for (auto v : vertex_lists_) delete[] v; + vertex_lists_.clear(); + } + + //------------------------------------------------------------------------------ + // ClipperBase methods ... + //------------------------------------------------------------------------------ + + ClipperBase::~ClipperBase() + { + Clear(); + } + + void ClipperBase::DeleteEdges(Active*& e) + { + while (e) + { + Active* e2 = e; + e = e->next_in_ael; + delete e2; + } + } + void ClipperBase::CleanUp() + { + DeleteEdges(actives_); + scanline_list_ = std::priority_queue(); + intersect_nodes_.clear(); + DisposeAllOutRecs(); + horz_seg_list_.clear(); + horz_join_list_.clear(); + } + + + void ClipperBase::Clear() + { + CleanUp(); + DisposeVerticesAndLocalMinima(); + current_locmin_iter_ = minima_list_.begin(); + minima_list_sorted_ = false; + has_open_paths_ = false; + } + + + void ClipperBase::Reset() + { + if (!minima_list_sorted_) + { + std::sort(minima_list_.begin(), minima_list_.end(), LocMinSorter()); + minima_list_sorted_ = true; + } + LocalMinimaList::const_reverse_iterator i; + for (i = minima_list_.rbegin(); i != minima_list_.rend(); ++i) + InsertScanline((*i)->vertex->pt.y); + + current_locmin_iter_ = minima_list_.begin(); + actives_ = nullptr; + sel_ = nullptr; + succeeded_ = true; + } + + +#ifdef USINGZ + void ClipperBase::SetZ(const Active& e1, const Active& e2, Point64& ip) + { + if (!zCallback_) return; + // prioritize subject over clip vertices by passing + // subject vertices before clip vertices in the callback + if (GetPolyType(e1) == PathType::Subject) + { + if (ip == e1.bot) ip.z = e1.bot.z; + else if (ip == e1.top) ip.z = e1.top.z; + else if (ip == e2.bot) ip.z = e2.bot.z; + else if (ip == e2.top) ip.z = e2.top.z; + else ip.z = DefaultZ; + zCallback_(e1.bot, e1.top, e2.bot, e2.top, ip); + } + else + { + if (ip == e2.bot) ip.z = e2.bot.z; + else if (ip == e2.top) ip.z = e2.top.z; + else if (ip == e1.bot) ip.z = e1.bot.z; + else if (ip == e1.top) ip.z = e1.top.z; + else ip.z = DefaultZ; + zCallback_(e2.bot, e2.top, e1.bot, e1.top, ip); + } + } +#endif + + void ClipperBase::AddPath(const Path64& path, PathType polytype, bool is_open) + { + Paths64 tmp; + tmp.push_back(path); + AddPaths(tmp, polytype, is_open); + } + + void ClipperBase::AddPaths(const Paths64& paths, PathType polytype, bool is_open) + { + if (is_open) has_open_paths_ = true; + minima_list_sorted_ = false; + AddPaths_(paths, polytype, is_open, vertex_lists_, minima_list_); + } + + void ClipperBase::AddReuseableData(const ReuseableDataContainer64& reuseable_data) + { + // nb: reuseable_data will continue to own the vertices + // and remains responsible for their clean up. + succeeded_ = false; + minima_list_sorted_ = false; + LocalMinimaList::const_iterator i; + for (i = reuseable_data.minima_list_.cbegin(); i != reuseable_data.minima_list_.cend(); ++i) + { + minima_list_.push_back(std::make_unique ((*i)->vertex, (*i)->polytype, (*i)->is_open)); + if ((*i)->is_open) has_open_paths_ = true; + } + } void ClipperBase::InsertScanline(int64_t y) { diff --git a/CSharp/Clipper2Lib/Clipper.Core.cs b/CSharp/Clipper2Lib/Clipper.Core.cs index ed834df0..8ba9e85b 100644 --- a/CSharp/Clipper2Lib/Clipper.Core.cs +++ b/CSharp/Clipper2Lib/Clipper.Core.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 21 February 2023 * +* Date : 1 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core structures and functions for the Clipper Library * @@ -211,9 +211,9 @@ public PointD(double x, double y, long z = 0) this.z = z; } - public override string ToString() + public string ToString(int precision = 2) { - return $"{x:F},{y:F},{z} "; + return string.Format($"{{0:F{precision}}},{{1:F{precision}}},{{2:D}}", x,y,z); } #else @@ -253,9 +253,9 @@ public PointD(double x, double y) this.y = y; } - public override string ToString() + public string ToString(int precision = 2) { - return $"{x:F},{y:F} "; + return string.Format($"{{0:F{precision}}},{{1:F{precision}}}", x,y); } #endif @@ -289,12 +289,9 @@ public struct Rect64 public long top; public long right; public long bottom; - private static readonly string InvalidRect = "Invalid Rect64 assignment"; public Rect64(long l, long t, long r, long b) { - if (r < l || b < t) - throw new Exception(InvalidRect); left = l; top = t; right = r; @@ -382,12 +379,9 @@ public struct RectD public double top; public double right; public double bottom; - private static readonly string InvalidRect = "Invalid RectD assignment"; public RectD(double l, double t, double r, double b) { - if (r < l || b < t) - throw new Exception(InvalidRect); left = l; top = t; right = r; @@ -501,11 +495,11 @@ public class PathD : List private PathD() : base() { } public PathD(int capacity = 0) : base(capacity) { } public PathD(IEnumerable path) : base(path) { } - public override string ToString() + public string ToString(int precision = 2) { string s = ""; foreach (PointD p in this) - s = s + p.ToString() + " "; + s = s + p.ToString(precision) + " "; return s; } } @@ -515,11 +509,11 @@ public class PathsD : List private PathsD() : base() { } public PathsD(int capacity = 0) : base(capacity) { } public PathsD(IEnumerable paths) : base(paths) { } - public override string ToString() + public string ToString(int precision = 2) { string s = ""; foreach (PathD p in this) - s = s + p.ToString() + "\n"; + s = s + p.ToString(precision) + "\n"; return s; } } diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 19a6b3a1..9c887028 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 23 April 2023 * +* Date : 1 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -87,7 +87,6 @@ public override int GetHashCode() { return vertex.GetHashCode(); } - }; // IntersectNode: a structure representing 2 intersecting edges. @@ -228,6 +227,134 @@ internal class Active internal JoinWith joinWith; }; + internal static class ClipperEngine + { + internal static void AddLocMin(Vertex vert, PathType polytype, bool isOpen, + List minimaList) + { + // make sure the vertex is added only once ... + if ((vert.flags & VertexFlags.LocalMin) != VertexFlags.None) return; + vert.flags |= VertexFlags.LocalMin; + + LocalMinima lm = new LocalMinima(vert, polytype, isOpen); + minimaList.Add(lm); + } + + internal static void AddPathsToVertexList(Paths64 paths, PathType polytype, bool isOpen, + List minimaList, List vertexList) + { + int totalVertCnt = 0; + foreach (Path64 path in paths) totalVertCnt += path.Count; + vertexList.Capacity = vertexList.Count + totalVertCnt; + + foreach (Path64 path in paths) + { + Vertex? v0 = null, prev_v = null, curr_v; + foreach (Point64 pt in path) + { + if (v0 == null) + { + v0 = new Vertex(pt, VertexFlags.None, null); + vertexList.Add(v0); + prev_v = v0; + } + else if (prev_v!.pt != pt) // ie skips duplicates + { + curr_v = new Vertex(pt, VertexFlags.None, prev_v); + vertexList.Add(curr_v); + prev_v.next = curr_v; + prev_v = curr_v; + } + } + if (prev_v == null || 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; + if (isOpen) + { + curr_v = v0.next; + while (curr_v != v0 && curr_v!.pt.Y == v0.pt.Y) + curr_v = curr_v.next; + going_up = curr_v.pt.Y <= v0.pt.Y; + if (going_up) + { + v0.flags = VertexFlags.OpenStart; + AddLocMin(v0, polytype, true, minimaList); + } + else + v0.flags = VertexFlags.OpenStart | VertexFlags.LocalMax; + } + else // closed path + { + prev_v = v0.prev; + while (prev_v != v0 && prev_v!.pt.Y == v0.pt.Y) + prev_v = prev_v.prev; + if (prev_v == v0) + continue; // only open paths can be completely flat + going_up = prev_v.pt.Y > v0.pt.Y; + } + + going_up0 = going_up; + prev_v = v0; + curr_v = v0.next; + while (curr_v != v0) + { + if (curr_v!.pt.Y > prev_v.pt.Y && going_up) + { + prev_v.flags |= VertexFlags.LocalMax; + going_up = false; + } + else if (curr_v.pt.Y < prev_v.pt.Y && !going_up) + { + going_up = true; + AddLocMin(prev_v, polytype, isOpen, minimaList); + } + prev_v = curr_v; + curr_v = curr_v.next; + } + + if (isOpen) + { + prev_v.flags |= VertexFlags.OpenEnd; + if (going_up) + prev_v.flags |= VertexFlags.LocalMax; + else + AddLocMin(prev_v, polytype, isOpen, minimaList); + } + else if (going_up != going_up0) + { + if (going_up0) AddLocMin(prev_v, polytype, false, minimaList); + else prev_v.flags |= VertexFlags.LocalMax; + } + } + } + } + + public class ReuseableDataContainer64 + { + internal readonly List _minimaList; + internal readonly List _vertexList; + public ReuseableDataContainer64() + { + _minimaList = new List(); + _vertexList = new List(); + } + public void Clear() + { + _minimaList.Clear(); + _vertexList.Clear(); + } + + public void AddPaths(Paths64 paths, PathType pt, bool isOpen) + { + ClipperEngine.AddPathsToVertexList(paths, pt, isOpen, _minimaList, _vertexList); + } + } + public class ClipperBase { private ClipType _cliptype; @@ -720,99 +847,7 @@ private void AddLocMin(Vertex vert, PathType polytype, bool isOpen) LocalMinima lm = new LocalMinima(vert, polytype, isOpen); _minimaList.Add(lm); } - - protected void AddPathsToVertexList(Paths64 paths, PathType polytype, bool isOpen) - { - int totalVertCnt = 0; - foreach (Path64 path in paths) totalVertCnt += path.Count; - _vertexList.Capacity = _vertexList.Count + totalVertCnt; - - foreach (Path64 path in paths) - { - Vertex? v0 = null, prev_v = null, curr_v; - foreach (Point64 pt in path) - { - if (v0 == null) - { - v0 = new Vertex(pt, VertexFlags.None, null); - _vertexList.Add(v0); - prev_v = v0; - } - else if (prev_v!.pt != pt) // ie skips duplicates - { - curr_v = new Vertex(pt, VertexFlags.None, prev_v); - _vertexList.Add(curr_v); - prev_v.next = curr_v; - prev_v = curr_v; - } - } - if (prev_v == null || 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; - if (isOpen) - { - curr_v = v0.next; - while (curr_v != v0 && curr_v!.pt.Y == v0.pt.Y) - curr_v = curr_v.next; - going_up = curr_v.pt.Y <= v0.pt.Y; - if (going_up) - { - v0.flags = VertexFlags.OpenStart; - AddLocMin(v0, polytype, true); - } - else - v0.flags = VertexFlags.OpenStart | VertexFlags.LocalMax; - } - else // closed path - { - prev_v = v0.prev; - while (prev_v != v0 && prev_v!.pt.Y == v0.pt.Y) - prev_v = prev_v.prev; - if (prev_v == v0) - continue; // only open paths can be completely flat - going_up = prev_v.pt.Y > v0.pt.Y; - } - - going_up0 = going_up; - prev_v = v0; - curr_v = v0.next; - while (curr_v != v0) - { - if (curr_v!.pt.Y > prev_v.pt.Y && going_up) - { - prev_v.flags |= VertexFlags.LocalMax; - going_up = false; - } - else if (curr_v.pt.Y < prev_v.pt.Y && !going_up) - { - going_up = true; - AddLocMin(prev_v, polytype, isOpen); - } - prev_v = curr_v; - curr_v = curr_v.next; - } - - if (isOpen) - { - prev_v.flags |= VertexFlags.OpenEnd; - if (going_up) - prev_v.flags |= VertexFlags.LocalMax; - else - AddLocMin(prev_v, polytype, isOpen); - } - else if (going_up != going_up0) - { - if (going_up0) AddLocMin(prev_v, polytype, false); - else prev_v.flags |= VertexFlags.LocalMax; - } - } - } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddSubject(Path64 path) { @@ -843,7 +878,22 @@ protected void AddPaths(Paths64 paths, PathType polytype, bool isOpen = false) { if (isOpen) _hasOpenPaths = true; _isSortedMinimaList = false; - AddPathsToVertexList(paths, polytype, isOpen); + ClipperEngine.AddPathsToVertexList(paths, polytype, isOpen, _minimaList, _vertexList); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void AddReuseableData(ReuseableDataContainer64 reuseableData) + { + if (reuseableData._minimaList.Count == 0) return; + // nb: reuseableData will continue to own the vertices, so it's important + // that the reuseableData object isn't destroyed before the Clipper object + // that's using the data. + _isSortedMinimaList = false; + foreach (LocalMinima lm in reuseableData._minimaList) + { + _minimaList.Add(new LocalMinima(lm.vertex, lm.polytype, lm.isOpen)); + if (lm.isOpen) _hasOpenPaths = true; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -3059,6 +3109,12 @@ public class Clipper64 : ClipperBase base.AddPath(path, polytype, isOpen); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public new void AddReuseableData(ReuseableDataContainer64 reuseableData) + { + base.AddReuseableData(reuseableData); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal new void AddPaths(Paths64 paths, PathType polytype, bool isOpen = false) { @@ -3180,7 +3236,7 @@ private void ZCB(Point64 bot1, Point64 top1, // temporarily convert integers to their initial float values // this will slow clipping marginally but will make it much easier // to understand the coordinates passed to the callback function - PointD tmp = new PointD(intersectPt); + PointD tmp = Clipper.ScalePointD(intersectPt, _invScale); //do the callback ZCallback?.Invoke( Clipper.ScalePointD(bot1, _invScale), diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index 84fa1477..cd783e1c 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 23 April 2023 * +* Date : 1 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -53,6 +53,18 @@ TLocMinList = class(TListEx) procedure Clear; override; end; + TReuseableDataContainer64 = class + private + FLocMinList : TLocMinList; + FVertexArrayList : TList; + public + constructor Create; + destructor Destroy; override; + procedure Clear; + procedure AddPaths(const paths: TPaths64; + pathType: TPathType; isOpen: Boolean); + end; + // forward declarations POutRec = ^TOutRec; PHorzSegment = ^THorzSegment; @@ -265,6 +277,7 @@ TClipperBase = class pathType: TPathType; isOpen: Boolean); procedure AddPaths(const paths: TPaths64; pathType: TPathType; isOpen: Boolean); + procedure AddReuseableData(const reuseableData: TReuseableDataContainer64); function ClearSolutionOnly: Boolean; procedure ExecuteInternal(clipType: TClipType; fillRule: TFillRule; usingPolytree: Boolean); @@ -289,6 +302,7 @@ TClipperBase = class TClipper64 = class(TClipperBase) // for integer coordinates public + procedure AddReuseableData(const reuseableData: TReuseableDataContainer64); procedure AddSubject(const subject: TPath64); overload; procedure AddSubject(const subjects: TPaths64); overload; procedure AddOpenSubject(const subject: TPath64); overload; @@ -536,10 +550,16 @@ function IsOpen(e: PActive): Boolean; overload; {$IFDEF INLINING} inline; {$ENDI end; //------------------------------------------------------------------------------ -function IsOpenEnd(e: PActive): Boolean; overload; {$IFDEF INLINING} inline; {$ENDIF} +//function IsOpenEnd(e: PActive): Boolean; overload; {$IFDEF INLINING} inline; {$ENDIF} +//begin +// Result := e.locMin.isOpen and +// (e.vertTop.flags * [vfOpenStart, vfOpenEnd] <> []); +//end; +//------------------------------------------------------------------------------ + +function IsOpenEnd(v: PVertex): Boolean; overload; {$IFDEF INLINING} inline; {$ENDIF} begin - Result := e.locMin.isOpen and - (e.vertTop.flags * [vfOpenStart, vfOpenEnd] <> []); + Result := (v.flags * [vfOpenStart, vfOpenEnd] <> []); end; //------------------------------------------------------------------------------ @@ -1181,6 +1201,44 @@ procedure SetOwner(outrec, newOwner: POutRec); outrec.owner := newOwner; end; +//------------------------------------------------------------------------------ +// TReuseableDataContainer64 methods ... +//------------------------------------------------------------------------------ + +constructor TReuseableDataContainer64.Create; +begin + FLocMinList := TLocMinList.Create; + FVertexArrayList := TList.Create; +end; +//------------------------------------------------------------------------------ + +destructor TReuseableDataContainer64.Destroy; +begin + Clear; + FLocMinList.Free; + FVertexArrayList.Free; + inherited; +end; +//------------------------------------------------------------------------------ + +procedure TReuseableDataContainer64.Clear; +var + i: integer; +begin + FLocMinList.Clear; + for i := 0 to FVertexArrayList.Count -1 do + FreeMem(UnsafeGet(FVertexArrayList, i)); + FVertexArrayList.Clear; +end; +//------------------------------------------------------------------------------ + +procedure TReuseableDataContainer64.AddPaths(const paths: TPaths64; + pathType: TPathType; isOpen: Boolean); +begin + AddPathsToVertexList(paths, pathType, isOpen, + FVertexArrayList, FLocMinList); +end; + //------------------------------------------------------------------------------ // TClipperBase methods ... //------------------------------------------------------------------------------ @@ -1402,6 +1460,29 @@ procedure TClipperBase.AddPaths(const paths: TPaths64; end; //------------------------------------------------------------------------------ +procedure TClipperBase.AddReuseableData(const reuseableData: TReuseableDataContainer64); +var + i: integer; + lm: PLocalMinima; +begin + if reuseableData.FLocMinList.Count = 0 then Exit; + // nb: reuseableData will continue to own the vertices + // and will remain responsible for their clean up. + // Consequently, it's important that the reuseableData object isn't + // destroyed before the Clipper object that's using the data. + FLocMinListSorted := false; + for i := 0 to reuseableData.FLocMinList.Count -1 do + with PLocalMinima(reuseableData.FLocMinList[i])^ do + begin + lm := self.FLocMinList.Add; + lm.vertex := vertex; + lm.polytype := polytype; + lm.isOpen := isOpen; + if isOpen then FHasOpenPaths := true; + end; +end; +//------------------------------------------------------------------------------ + function TClipperBase.IsContributingClosed(e: PActive): Boolean; begin Result := false; @@ -2035,9 +2116,9 @@ function TClipperBase.AddLocalMaxPoly(e1, e2: PActive; const pt: TPoint64): POut if (IsFront(e1) = IsFront(e2)) then begin - if IsOpenEnd(e1) then + if IsOpenEnd(e1.vertTop) then SwapFrontBackSides(e1.outrec) - else if IsOpenEnd(e2) then + else if IsOpenEnd(e2.vertTop) then SwapFrontBackSides(e2.outrec) else begin @@ -2118,7 +2199,7 @@ procedure TClipperBase.JoinOutrecPaths(e1, e2: PActive); e2.outrec.pts := nil; SetOwner(e2.outrec, e1.outrec); - if IsOpenEnd(e1) then + if IsOpenEnd(e1.vertTop) then begin e2.outrec.pts := e1.outrec.pts; e1.outrec.pts := nil; @@ -2910,7 +2991,7 @@ procedure TClipperBase.ProcessHorzJoins; var i: integer; or1, or2: POutRec; - op, op1b, op2b: POutPt; + op1b, op2b: POutPt; begin for i := 0 to FHorzJoinList.Count -1 do with PHorzJoin(FHorzJoinList[i])^ do @@ -3365,7 +3446,7 @@ procedure TClipperBase.DoHorizontal(horzEdge: PActive); // if horzEdge is a maxima, keep going until we reach // its maxima pair, otherwise check for Break conditions - if (maxVertex <> horzEdge.vertTop) or IsOpenEnd(horzEdge) then + if (maxVertex <> horzEdge.vertTop) or IsOpenEnd(horzEdge.vertTop) then begin // otherwise stop when 'e' is beyond the end of the horizontal line if (isLeftToRight and (e.currX > horzRight)) or @@ -3416,7 +3497,7 @@ procedure TClipperBase.DoHorizontal(horzEdge: PActive); end; // we've reached the end of this horizontal // check if we've finished looping through consecutive horizontals - if horzIsOpen and IsOpenEnd(horzEdge) then + if horzIsOpen and IsOpenEnd(horzEdge.vertTop) then begin if IsHotEdge(horzEdge) then begin @@ -3494,7 +3575,7 @@ function TClipperBase.DoMaxima(e: PActive): PActive; eNext := e.nextInAEL; Result := eNext; - if IsOpenEnd(e) then + if IsOpenEnd(e.vertTop) then begin if IsHotEdge(e) then AddOutPt(e, e.top); if not IsHorizontal(e) then @@ -3737,6 +3818,12 @@ function TClipperBase.GetBounds: TRect64; // TClipper methods //------------------------------------------------------------------------------ +procedure TClipper64.AddReuseableData(const reuseableData: TReuseableDataContainer64); +begin + inherited AddReuseableData(reuseableData); +end; +//------------------------------------------------------------------------------ + procedure TClipper64.AddSubject(const subject: TPath64); begin AddPath(subject, ptSubject, false); diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index 1c0bb2e5..d1d14e06 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -561,9 +561,6 @@ procedure TClipperOffset.ExecuteInternal(delta: Double); //------------------------------------------------------------------------------ procedure TClipperOffset.Execute(delta: Double; out solution: TPaths64); -var - i: integer; - group: TGroup; begin fSolution := nil; solution := nil; @@ -589,8 +586,6 @@ procedure TClipperOffset.Execute(delta: Double; out solution: TPaths64); procedure TClipperOffset.Execute(delta: Double; polytree: TPolyTree64); var - i: integer; - group: TGroup; dummy: TPaths64; begin fSolution := nil; From 6e15ba03b6150d3d3c7d9db055eab49baf9b280f Mon Sep 17 00:00:00 2001 From: angusj Date: Tue, 16 May 2023 15:41:55 +1000 Subject: [PATCH 12/95] ClipperOffset added support for variable offsets (#511) further reduced extraneous vertices in solutions (#499) ClipperEngine Fixed a minor bug with polytrees (#520) --- CPP/CMakeLists.txt | 2 + .../include/clipper2/clipper.core.h | 2 +- .../include/clipper2/clipper.engine.h | 18 +- CPP/Clipper2Lib/include/clipper2/clipper.h | 47 +++-- .../include/clipper2/clipper.offset.h | 10 +- CPP/Clipper2Lib/src/clipper.engine.cpp | 23 ++- CPP/Clipper2Lib/src/clipper.offset.cpp | 184 +++++++++++------- .../VariableOffset/VariableOffset.cpp | 139 +++++++++++++ CPP/Tests/TestPolytreeHoles4.cpp | 26 +++ CSharp/Clipper2Lib/Clipper.Engine.cs | 28 +-- CSharp/Clipper2Lib/Clipper.Offset.cs | 184 +++++++++++------- Delphi/Clipper2Lib/Clipper.Engine.pas | 31 ++- Delphi/Clipper2Lib/Clipper.Offset.pas | 87 +++++++-- 13 files changed, 557 insertions(+), 224 deletions(-) create mode 100644 CPP/Examples/VariableOffset/VariableOffset.cpp create mode 100644 CPP/Tests/TestPolytreeHoles4.cpp diff --git a/CPP/CMakeLists.txt b/CPP/CMakeLists.txt index b1e37afa..ad167700 100644 --- a/CPP/CMakeLists.txt +++ b/CPP/CMakeLists.txt @@ -137,6 +137,7 @@ if(CLIPPER2_EXAMPLES) UnionClipping RectClipping SimpleClipping + VariableOffset ) foreach(ex ${EXAMPLES}) @@ -190,6 +191,7 @@ endif() Tests/TestPolytreeHoles1.cpp Tests/TestPolytreeHoles2.cpp Tests/TestPolytreeHoles3.cpp + Tests/TestPolytreeHoles4.cpp Tests/TestPolytreeIntersection.cpp Tests/TestPolytreeUnion.cpp Tests/TestRandomPaths.cpp diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index e4442a68..9fd53d03 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -428,7 +428,7 @@ namespace Clipper2Lib } template - inline Path ScalePath(const Path& path, + inline Path ScalePath(const Path& path, double scale, int& error_code) { return ScalePath(path, scale, scale, error_code); diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h index 840d1f50..3c049fc5 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 1 May 2023 * +* Date : 15 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -270,7 +270,7 @@ namespace Clipper2Lib { bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees); void CleanCollinear(OutRec* outrec); bool CheckBounds(OutRec* outrec); - bool CheckSplitOwner(OutRec* outrec); + bool CheckSplitOwner(OutRec* outrec, OutRecList* splits); void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath); #ifdef USINGZ ZCallback64 zCallback_ = nullptr; @@ -347,7 +347,7 @@ namespace Clipper2Lib { const PolyPath64* operator [] (size_t index) const { - return childs_[index].get(); + return childs_[index].get(); //std::unique_ptr } const PolyPath64* Child(size_t index) const @@ -390,12 +390,12 @@ namespace Clipper2Lib { class PolyPathD : public PolyPath { private: PolyPathDList childs_; - double inv_scale_; + double scale_; PathD polygon_; public: explicit PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent) { - inv_scale_ = parent ? parent->inv_scale_ : 1.0; + scale_ = parent ? parent->scale_ : 1.0; } ~PolyPathD() { @@ -415,14 +415,14 @@ namespace Clipper2Lib { PolyPathDList::const_iterator begin() const { return childs_.cbegin(); } PolyPathDList::const_iterator end() const { return childs_.cend(); } - void SetInvScale(double value) { inv_scale_ = value; } - double InvScale() { return inv_scale_; } + void SetScale(double value) { scale_ = value; } + double Scale() { return scale_; } 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, inv_scale_, error_code); + result->polygon_ = ScalePath(path, scale_, error_code); return result; } @@ -610,7 +610,7 @@ namespace Clipper2Lib { if (ExecuteInternal(clip_type, fill_rule, true)) { polytree.Clear(); - polytree.SetInvScale(invScale_); + polytree.SetScale(invScale_); open_paths.clear(); BuildTreeD(polytree, open_paths); } diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.h b/CPP/Clipper2Lib/include/clipper2/clipper.h index f406eaef..937e80f7 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 21 April 2023 * +* Date : 15 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -161,40 +161,44 @@ namespace Clipper2Lib { return ScalePaths(solution, 1 / scale, error_code); } - inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy) + template + inline Path TranslatePath(const Path& path, T dx, T dy) { - Path64 result; + Path result; result.reserve(path.size()); std::transform(path.begin(), path.end(), back_inserter(result), - [dx, dy](const auto& pt) { return Point64(pt.x + dx, pt.y +dy); }); + [dx, dy](const auto& pt) { return Point(pt.x + dx, pt.y +dy); }); return result; } + inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy) + { + return TranslatePath(path, dx, dy); + } + inline PathD TranslatePath(const PathD& path, double dx, double dy) { - PathD result; - result.reserve(path.size()); - std::transform(path.begin(), path.end(), back_inserter(result), - [dx, dy](const auto& pt) { return PointD(pt.x + dx, pt.y + dy); }); - return result; + return TranslatePath(path, dx, dy); } - inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy) + template + inline Paths TranslatePaths(const Paths& paths, T dx, T dy) { - Paths64 result; + Paths result; result.reserve(paths.size()); std::transform(paths.begin(), paths.end(), back_inserter(result), [dx, dy](const auto& path) { return TranslatePath(path, dx, dy); }); return result; } + inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy) + { + return TranslatePaths(paths, dx, dy); + } + inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy) { - PathsD result; - result.reserve(paths.size()); - std::transform(paths.begin(), paths.end(), back_inserter(result), - [dx, dy](const auto& path) { return TranslatePath(path, dx, dy); }); - return result; + return TranslatePaths(paths, dx, dy); } inline Paths64 ExecuteRectClip(const Rect64& rect, @@ -290,14 +294,9 @@ namespace Clipper2Lib { { // return false if this child isn't fully contained by its parent - // the following algorithm is a bit too crude, and doesn't account - // for rounding errors. A better algorithm is to return false when - // consecutive vertices are found outside the parent's polygon. - - //const Path64& path = pp.Polygon(); - //if (std::any_of(child->Polygon().cbegin(), child->Polygon().cend(), - // [path](const auto& pt) {return (PointInPolygon(pt, path) == - // PointInPolygonResult::IsOutside); })) return false; + // checking for a single vertex outside is a bit too crude since + // it doesn't account for rounding errors. It's better to check + // for consecutive vertices found outside the parent's polygon. int outsideCnt = 0; for (const Point64& pt : child->Polygon()) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.offset.h b/CPP/Clipper2Lib/include/clipper2/clipper.offset.h index f5d47e07..8835fb0f 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.offset.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.offset.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 22 March 2023 * +* Date : 15 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -24,6 +24,7 @@ enum class EndType {Polygon, Joined, Butt, Square, Round}; //Joined : offsets both sides of a path, with joined ends //Polygon: offsets only one side of a closed path +typedef std::function DeltaCallback64; class ClipperOffset { private: @@ -43,7 +44,6 @@ class ClipperOffset { int error_code_ = 0; double delta_ = 0.0; double group_delta_ = 0.0; - double abs_group_delta_ = 0.0; double temp_lim_ = 0.0; double steps_per_rad_ = 0.0; double step_sin_ = 0.0; @@ -62,6 +62,7 @@ class ClipperOffset { #ifdef USINGZ ZCallback64 zCallback64_ = nullptr; #endif + DeltaCallback64 deltaCallback64_ = nullptr; void DoSquare(Group& group, const Path64& path, size_t j, size_t k); void DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a); @@ -70,7 +71,7 @@ class ClipperOffset { void OffsetPolygon(Group& group, Path64& path); void OffsetOpenJoined(Group& group, Path64& path); void OffsetOpenPath(Group& group, Path64& path); - void OffsetPoint(Group& group, Path64& path, size_t j, size_t& k); + void OffsetPoint(Group& group, Path64& path, size_t j, size_t k); void DoGroupOffset(Group &group); void ExecuteInternal(double delta); public: @@ -91,6 +92,7 @@ class ClipperOffset { void Execute(double delta, Paths64& paths); void Execute(double delta, PolyTree64& polytree); + void Execute(DeltaCallback64 delta_cb, Paths64& paths); double MiterLimit() const { return miter_limit_; } void MiterLimit(double miter_limit) { miter_limit_ = miter_limit; } @@ -108,6 +110,8 @@ class ClipperOffset { #ifdef USINGZ void SetZCallback(ZCallback64 cb) { zCallback64_ = cb; } #endif + void SetDeltaCallback(DeltaCallback64 cb) { deltaCallback64_ = cb; } + }; } diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index 141c47eb..e29f2fe6 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 1 May 2023 * +* Date : 16 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -1378,6 +1378,7 @@ namespace Clipper2Lib { result->owner = nullptr; result->polypath = nullptr; result->is_open = false; + result->splits = nullptr; return result; } @@ -2174,7 +2175,7 @@ namespace Clipper2Lib { if (or1 == or2) { - or2 = new OutRec(); + or2 = NewOutRec(); or2->pts = op1b; FixOutRecPts(or2); if (or1->pts->outrec == or2) @@ -2186,7 +2187,11 @@ namespace Clipper2Lib { if (using_polytree_) { if (Path1InsidePath2(or2->pts, or1->pts)) + { SetOwner(or2, or1); + if (!or1->splits) or1->splits = new OutRecList(); + or1->splits->push_back(or2); //(#520) + } else if (Path1InsidePath2(or1->pts, or2->pts)) SetOwner(or1, or2); else @@ -2198,8 +2203,6 @@ namespace Clipper2Lib { } else or2->owner = or1; - - outrec_list_.push_back(or2); } else { @@ -2826,14 +2829,14 @@ namespace Clipper2Lib { return true; } - bool ClipperBase::CheckSplitOwner(OutRec* outrec) + bool ClipperBase::CheckSplitOwner(OutRec* outrec, OutRecList* splits) { - for (auto s : *outrec->owner->splits) + for (auto s : *splits) { OutRec* split = GetRealOutRec(s); - if (split && split != outrec && - split != outrec->owner && CheckBounds(split) && - split->bounds.Contains(outrec->bounds) && + if (!split || split == outrec || split == outrec->owner) continue; + else if (split->splits && CheckSplitOwner(outrec, split->splits)) return true; + else if (CheckBounds(split) && split->bounds.Contains(outrec->bounds) && Path1InsidePath2(outrec->pts, split->pts)) { outrec->owner = split; //found in split @@ -2852,7 +2855,7 @@ namespace Clipper2Lib { while (outrec->owner) { - if (outrec->owner->splits && CheckSplitOwner(outrec)) break; + if (outrec->owner->splits && CheckSplitOwner(outrec, outrec->owner->splits)) break; if (outrec->owner->pts && CheckBounds(outrec->owner) && outrec->owner->bounds.Contains(outrec->bounds) && Path1InsidePath2(outrec->pts, outrec->owner->pts)) break; diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index a60cf05b..5baa466a 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 8 April 2023 * +* Date : 15 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -211,9 +211,11 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t PointD(-norms[k].y, norms[k].x), PointD(norms[j].y, -norms[j].x)); + double abs_delta = std::abs(group_delta_); + // now offset the original vertex delta units along unit vector PointD ptQ = PointD(path[j]); - ptQ = TranslatePoint(ptQ, abs_group_delta_ * vec.x, abs_group_delta_ * vec.y); + ptQ = TranslatePoint(ptQ, abs_delta * vec.x, abs_delta * vec.y); // get perpendicular vertices PointD pt1 = TranslatePoint(ptQ, group_delta_ * vec.y, group_delta_ * -vec.x); PointD pt2 = TranslatePoint(ptQ, group_delta_ * -vec.y, group_delta_ * vec.x); @@ -260,6 +262,20 @@ void ClipperOffset::DoMiter(Group& group, const Path64& path, size_t j, size_t k void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle) { + if (deltaCallback64_) { + // when deltaCallback64_ is assigned, group_delta_ won't be constant, + // so we'll need to do the following calculations for *every* vertex. + double abs_delta = std::fabs(group_delta_); + double arcTol = (arc_tolerance_ > floating_point_tolerance ? + std::min(abs_delta, arc_tolerance_) : + std::log10(2 + abs_delta) * default_arc_tolerance); + double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI); + step_sin_ = std::sin(2 * PI / steps_per_360); + step_cos_ = std::cos(2 * PI / steps_per_360); + if (group_delta_ < 0.0) step_sin_ = -step_sin_; + steps_per_rad_ = steps_per_360 / (2 * PI); + } + Point64 pt = path[j]; PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_); @@ -287,7 +303,7 @@ void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); } -void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k) +void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) { // Let A = change in angle where edges join // A == 0: ie no change in angle (flat join) @@ -302,7 +318,16 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k) if (sin_a > 1.0) sin_a = 1.0; else if (sin_a < -1.0) sin_a = -1.0; - if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) + if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, j, k); + if (std::fabs(group_delta_) <= floating_point_tolerance) + { + group.path.push_back(path[j]); + return; + } + + if (cos_a > 0.99) // 0.99 ~= 8.1 deg. + DoMiter(group, path, j, k, cos_a); + else if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) { // is concave group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_)); @@ -317,22 +342,16 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k) if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a); else DoSquare(group, path, j, k); } - else if (cos_a > 0.9998) - // almost straight - less than 1 degree (#424) - DoMiter(group, path, j, k, cos_a); - else if (cos_a > 0.99 || join_type_ == JoinType::Square) - //angle less than 8 degrees or squared joins + else if (join_type_ == JoinType::Square) DoSquare(group, path, j, k); else DoRound(group, path, j, k, std::atan2(sin_a, cos_a)); - - k = j; } void ClipperOffset::OffsetPolygon(Group& group, Path64& path) { - for (Path64::size_type i = 0, j = path.size() -1; i < path.size(); j = i, ++i) - OffsetPoint(group, path, i, j); + for (Path64::size_type j = 0, k = path.size() -1; j < path.size(); k = j, ++j) + OffsetPoint(group, path, j, k); group.paths_out.push_back(group.path); } @@ -354,34 +373,40 @@ void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path) void ClipperOffset::OffsetOpenPath(Group& group, Path64& path) { // do the line start cap - switch (end_type_) + if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0); + + if (std::fabs(group_delta_) <= floating_point_tolerance) + group.path.push_back(path[0]); + else { - case EndType::Butt: + switch (end_type_) + { + case EndType::Butt: #ifdef USINGZ - group.path.push_back(Point64( - path[0].x - norms[0].x * group_delta_, - path[0].y - norms[0].y * group_delta_, - path[0].z)); + group.path.push_back(Point64( + path[0].x - norms[0].x * group_delta_, + path[0].y - norms[0].y * group_delta_, + path[0].z)); #else - group.path.push_back(Point64( - path[0].x - norms[0].x * group_delta_, - path[0].y - norms[0].y * group_delta_)); + group.path.push_back(Point64( + path[0].x - norms[0].x * group_delta_, + path[0].y - norms[0].y * group_delta_)); #endif - group.path.push_back(GetPerpendic(path[0], norms[0], group_delta_)); - break; - case EndType::Round: - DoRound(group, path, 0,0, PI); - break; - default: - DoSquare(group, path, 0, 0); - break; + group.path.push_back(GetPerpendic(path[0], norms[0], group_delta_)); + break; + case EndType::Round: + DoRound(group, path, 0, 0, PI); + break; + default: + DoSquare(group, path, 0, 0); + break; + } } - + size_t highI = path.size() - 1; - // offset the left side going forward - for (Path64::size_type i = 1, k = 0; i < highI; ++i) - OffsetPoint(group, path, i, k); + for (Path64::size_type j = 1, k = 0; j < highI; k = j, ++j) + OffsetPoint(group, path, j, k); // reverse normals for (size_t i = highI; i > 0; --i) @@ -389,31 +414,39 @@ void ClipperOffset::OffsetOpenPath(Group& group, Path64& path) norms[0] = norms[highI]; // do the line end cap - switch (end_type_) + if (deltaCallback64_) + group_delta_ = deltaCallback64_(path, norms, highI, highI); + + if (std::fabs(group_delta_) <= floating_point_tolerance) + group.path.push_back(path[highI]); + else { - case EndType::Butt: + switch (end_type_) + { + case EndType::Butt: #ifdef USINGZ - group.path.push_back(Point64( - path[highI].x - norms[highI].x * group_delta_, - path[highI].y - norms[highI].y * group_delta_, - path[highI].z)); + group.path.push_back(Point64( + path[highI].x - norms[highI].x * group_delta_, + path[highI].y - norms[highI].y * group_delta_, + path[highI].z)); #else - group.path.push_back(Point64( - path[highI].x - norms[highI].x * group_delta_, - path[highI].y - norms[highI].y * group_delta_)); + group.path.push_back(Point64( + path[highI].x - norms[highI].x * group_delta_, + path[highI].y - norms[highI].y * group_delta_)); #endif - group.path.push_back(GetPerpendic(path[highI], norms[highI], group_delta_)); - break; - case EndType::Round: - DoRound(group, path, highI, highI, PI); - break; - default: - DoSquare(group, path, highI, highI); - break; + group.path.push_back(GetPerpendic(path[highI], norms[highI], group_delta_)); + break; + case EndType::Round: + DoRound(group, path, highI, highI, PI); + break; + default: + DoSquare(group, path, highI, highI); + break; + } } - for (size_t i = highI, k = 0; i > 0; --i) - OffsetPoint(group, path, i, k); + for (size_t j = highI, k = 0; j > 0; k = j, --j) + OffsetPoint(group, path, j, k); group.paths_out.push_back(group.path); } @@ -439,10 +472,10 @@ void ClipperOffset::DoGroupOffset(Group& group) group.is_reversed = false; group_delta_ = std::abs(delta_) * 0.5; } - abs_group_delta_ = std::fabs(group_delta_); + double abs_delta = std::fabs(group_delta_); // do range checking - if (!IsSafeOffset(r, abs_group_delta_)) + if (!IsSafeOffset(r, abs_delta)) { DoError(range_error_i); error_code_ |= range_error_i; @@ -455,21 +488,22 @@ void ClipperOffset::DoGroupOffset(Group& group) //calculate a sensible number of steps (for 360 deg for the given offset if (group.join_type == JoinType::Round || group.end_type == EndType::Round) { - // arcTol - when arc_tolerance_ is undefined (0), the amount of - // curve imprecision that's allowed is based on the size of the - // offset (delta). Obviously very large offsets will almost always - // require much less precision. See also offset_triginometry2.svg - double arcTol = (arc_tolerance_ > floating_point_tolerance ? - std::min(abs_group_delta_, arc_tolerance_) : - std::log10(2 + abs_group_delta_) * default_arc_tolerance); - double steps_per_360 = PI / std::acos(1 - arcTol / abs_group_delta_); - if (steps_per_360 > abs_group_delta_ * PI) - steps_per_360 = abs_group_delta_ * PI; //ie avoids excessive precision - step_sin_ = std::sin(2 * PI / steps_per_360); - step_cos_ = std::cos(2 * PI / steps_per_360); - if (group_delta_ < 0.0) step_sin_ = -step_sin_; - steps_per_rad_ = steps_per_360 / (2 *PI); + if (!deltaCallback64_) { + // arcTol - when arc_tolerance_ is undefined (0), the amount of + // curve imprecision that's allowed is based on the size of the + // offset (delta). Obviously very large offsets will almost always + // require much less precision. See also offset_triginometry2.svg + double arcTol = (arc_tolerance_ > floating_point_tolerance ? + std::min(abs_delta, arc_tolerance_) : + std::log10(2 + abs_delta) * default_arc_tolerance); + + double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI); + step_sin_ = std::sin(2 * PI / steps_per_360); + step_cos_ = std::cos(2 * PI / steps_per_360); + if (group_delta_ < 0.0) step_sin_ = -step_sin_; + steps_per_rad_ = steps_per_360 / (2 * PI); + } } bool is_joined = @@ -491,7 +525,7 @@ void ClipperOffset::DoGroupOffset(Group& group) //single vertex so build a circle or square ... if (group.join_type == JoinType::Round) { - double radius = abs_group_delta_; + double radius = abs_delta; group.path = Ellipse(path[0], radius, radius); #ifdef USINGZ for (auto& p : group.path) p.z = path[0].z; @@ -499,7 +533,7 @@ void ClipperOffset::DoGroupOffset(Group& group) } else { - int d = (int)std::ceil(abs_group_delta_); + int d = (int)std::ceil(abs_delta); r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d); group.path = r.AsPath(); #ifdef USINGZ @@ -568,6 +602,7 @@ void ClipperOffset::Execute(double delta, Paths64& paths) if (!solution.size()) return; paths = solution; + /**/ //clean up self-intersections ... Clipper64 c; c.PreserveCollinear = false; @@ -583,6 +618,7 @@ void ClipperOffset::Execute(double delta, Paths64& paths) c.Execute(ClipType::Union, FillRule::Negative, paths); else c.Execute(ClipType::Union, FillRule::Positive, paths); +/**/ } @@ -610,4 +646,10 @@ void ClipperOffset::Execute(double delta, PolyTree64& polytree) c.Execute(ClipType::Union, FillRule::Positive, polytree); } +void ClipperOffset::Execute(DeltaCallback64 delta_cb, Paths64& paths) +{ + deltaCallback64_ = delta_cb; + Execute(1.0, paths); +} + } // namespace diff --git a/CPP/Examples/VariableOffset/VariableOffset.cpp b/CPP/Examples/VariableOffset/VariableOffset.cpp new file mode 100644 index 00000000..1571c702 --- /dev/null +++ b/CPP/Examples/VariableOffset/VariableOffset.cpp @@ -0,0 +1,139 @@ +#include + +#include "clipper2/clipper.h" +#include "clipper2/clipper.core.h" +#include "../../Utils/clipper.svg.utils.h" +#include "../../Utils/CommonUtils.h" + +using namespace Clipper2Lib; + +void System(const std::string& filename) +{ +#ifdef _WIN32 + system(filename.c_str()); +#else + system(("firefox " + filename).c_str()); +#endif +} + +void test1() { + + int64_t const scale = 10; + double delta = 10 * scale; + + ClipperOffset co; + co.SetDeltaCallback([delta](const Path64& path, + const PathD& path_norms, size_t curr_idx, size_t prev_idx) + { + // gradually scale down the offset to a minimum of 25% of delta + double high = static_cast(path.size() - 1) * 1.25; + return (high - curr_idx) / high * delta; + }); + + Path64 ellipse = Ellipse(Rect64(0, 0, 200 * scale, 180 * scale)); + size_t el_size = ellipse.size() * 0.9; + ellipse.resize(el_size); + Paths64 subject = { ellipse }; + + co.AddPaths(subject, JoinType::Miter, EndType::Round); + Paths64 solution; + co.Execute(1.0, solution); + + SvgWriter svg; + SvgAddOpenSubject(svg, subject, FillRule::NonZero); + SvgAddSolution(svg, solution, FillRule::NonZero, false); + SvgSaveToFile(svg, "c:\\temp\\tmp1.svg", 400, 400); + System("c:\\temp\\tmp1.svg"); +} + +void test2() { + + int64_t const scale = 10; + double delta = 10 * scale; + + ClipperOffset co; + co.SetDeltaCallback([delta](const Path64& path, + const PathD& path_norms, size_t curr_idx, size_t prev_idx) { + // calculate offset based on distance from the middle of the path + double mid_idx = static_cast(path.size()) / 2.0; + return delta * (1.0 - 0.70 * (std::fabs(curr_idx - mid_idx) / mid_idx)); + }); + + Path64 ellipse = Ellipse(Rect64(0, 0, 200 * scale, 180 * scale)); + size_t el_size = ellipse.size() * 0.9; + ellipse.resize(el_size); + Paths64 subject = { ellipse }; + + co.AddPaths(subject, JoinType::Miter, EndType::Round); + Paths64 solution; + co.Execute(1.0, solution); + + SvgWriter svg; + SvgAddOpenSubject(svg, subject, FillRule::NonZero); + SvgAddSolution(svg, solution, FillRule::NonZero, false); + SvgSaveToFile(svg, "c:\\temp\\tmp2.svg", 400, 400); + System("c:\\temp\\tmp2.svg"); +} + +void test3() { + + double radius = 5000.0; + Paths64 subject = { Ellipse(Rect64(-radius, -radius, radius, radius), 200) }; + + ClipperOffset co; + co.AddPaths(subject, JoinType::Miter, EndType::Polygon); + + co.SetDeltaCallback([radius](const Path64& path, + const PathD& path_norms, size_t curr_idx, size_t prev_idx) { + // when multiplying the x & y of edge unit normal vectors, the value will be + // largest (0.5) when edges are at 45 deg. and least (-0.5) at negative 45 deg. + double delta = path_norms[curr_idx].y * path_norms[curr_idx].x; + return radius * 0.5 + radius * delta; + }); + + // solution + Paths64 solution; + co.Execute(1.0, solution); + + SvgWriter svg; + SvgAddSubject(svg, subject, FillRule::NonZero); + SvgAddSolution(svg, solution, FillRule::NonZero, false); + SvgSaveToFile(svg, "c:\\temp\\tmp3.svg", 400, 400); + System("c:\\temp\\tmp3.svg"); +} + +void test4() { + + int64_t const scale = 100; + Paths64 solution; + Paths64 subject = { Ellipse(ScaleRect(Rect64(10, 10, 50, 50), scale)) }; + + ClipperOffset co; + co.AddPaths(subject, JoinType::Round, EndType::Round); + co.Execute( + [scale](const Path64& path, + const PathD& path_norms, size_t curr_idx, size_t prev_idx) { + //double vertex_sin_a = CrossProduct(path_norms[curr_idx], path_norms[prev_idx]); + //double vertex_cos_a = DotProduct(path_norms[curr_idx], path_norms[prev_idx]); + //double vertex_angle = std::atan2(vertex_sin_a, vertex_cos_a); + //double edge_angle = std::atan2(path_norms[curr_idx].y, path_norms[curr_idx].x); + double sin_edge = path_norms[curr_idx].y; + return Sqr(sin_edge) * 3 * scale; } + , solution); + + SvgWriter svg; + SvgAddOpenSubject(svg, subject, FillRule::NonZero); + SvgAddSolution(svg, solution, FillRule::NonZero, false); + SvgSaveToFile(svg, "c:\\temp\\tmp4.svg", 400, 400); + System("c:\\temp\\tmp4.svg"); +} + + +int main() { + + test1(); + test2(); + test3(); + test4(); + return 0; +} \ No newline at end of file diff --git a/CPP/Tests/TestPolytreeHoles4.cpp b/CPP/Tests/TestPolytreeHoles4.cpp new file mode 100644 index 00000000..290abddd --- /dev/null +++ b/CPP/Tests/TestPolytreeHoles4.cpp @@ -0,0 +1,26 @@ +#include +#include "clipper2/clipper.h" + +using namespace Clipper2Lib; + +TEST(Clipper2Tests, TestPolytreeHoles4) +{ + Paths64 subject; + PolyTree64 solution; + Clipper64 c; + subject.push_back(MakePath({ 50,500, 50,300, 100,300, 100,350, 150,350, + 150,250, 200,250, 200,450, 350,450, 350,200, 400,200, 400,225, 450,225, + 450,175, 400,175, 400,200, 350,200, 350,175, 200,175, 200,250, 150,250, + 150,200, 100,200, 100,300, 50,300, 50,125, 500,125, 500,500 })); + subject.push_back(MakePath({ 250,425, 250,375, 300,375, 300,425})); + + c.AddSubject(subject); + c.Execute(ClipType::Union, FillRule::NonZero, solution); + // Polytree root + // +- Polygon with 3 holes. + // +- Hole with 1 nested polygon. + // +-Polygon + // +- Hole + // +- Hole + EXPECT_TRUE(solution.Count() == 1 && solution[0]->Count() == 3); +} \ No newline at end of file diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 9c887028..995ca382 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 1 May 2023 * +* Date : 14 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2701,10 +2701,9 @@ private void ProcessHorzJoins() if (or1 == or2) { - or2 = new OutRec - { - pts = op1b - }; + or2 = NewOutRec(); + or2.pts = op1b; + FixOutRecPts(or2); if (or1.pts!.outrec == or2) { @@ -2715,7 +2714,11 @@ private void ProcessHorzJoins() if (_using_polytree) { if (Path1InsidePath2(or2.pts, or1.pts)) + { SetOwner(or2, or1); + or1.splits ??= new List(); + or1.splits.Add(or2.idx); // (#520) + } else if (Path1InsidePath2(or1.pts, or2.pts)) SetOwner(or1, or2); else @@ -2727,8 +2730,6 @@ private void ProcessHorzJoins() } else or2.owner = or1; - - _outrecList.Add(or2); } else { @@ -3009,14 +3010,14 @@ private bool CheckBounds(OutRec outrec) return true; } - private bool CheckSplitOwner(OutRec outrec) + private bool CheckSplitOwner(OutRec outrec, List? splits) { foreach (int i in outrec.owner!.splits!) { OutRec? split = GetRealOutRec(_outrecList[i]); - if (split != null && split != outrec && - split != outrec.owner && CheckBounds(split) && - split.bounds.Contains(outrec.bounds) && + if (split == null || split == outrec || split == outrec.owner) continue; + else if (split.splits != null && CheckSplitOwner(outrec, split.splits)) return true; + else if (CheckBounds(split) && split.bounds.Contains(outrec.bounds) && Path1InsidePath2(outrec.pts!, split.pts!)) { outrec.owner = split; //found in split @@ -3034,8 +3035,9 @@ private void RecursiveCheckOwners(OutRec outrec, PolyPathBase polypath) while (outrec.owner != null) { - if (outrec.owner.splits != null && CheckSplitOwner(outrec)) break; - if (outrec.owner.pts != null && CheckBounds(outrec.owner) && + if (outrec.owner.splits != null && + CheckSplitOwner(outrec, outrec.owner.splits)) break; + else if (outrec.owner.pts != null && CheckBounds(outrec.owner) && Path1InsidePath2(outrec.pts!, outrec.owner.pts!)) break; outrec.owner = outrec.owner.owner; } diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index ed6c3ceb..0716acc4 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 8 April 2023 * +* Date : 16 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -52,12 +52,12 @@ public Group(Paths64 paths, JoinType joinType, EndType endType = EndType.Polygon } } + private static double Tolerance = 1.0E-12; private readonly List _groupList = new List(); private readonly PathD _normals = new PathD(); private readonly Paths64 _solution = new Paths64(); - private double _group_delta; //*0.5 for open paths; *-1.0 for negative areas + private double _groupDelta; //*0.5 for open paths; *-1.0 for negative areas private double _delta; - private double _abs_group_delta; private double _mitLimSqr; private double _stepsPerRad; private double _stepSin; @@ -69,6 +69,11 @@ public Group(Paths64 paths, JoinType joinType, EndType endType = EndType.Polygon public double MiterLimit { get; set; } public bool PreserveCollinear { get; set; } public bool ReverseSolution { get; set; } + + public delegate double DeltaCallback64(Path64 path, + PathD path_norms, int currPt, int prevPt); + public ClipperOffset.DeltaCallback64? DeltaCallback { get; set; } + #if USINGZ public ClipperBase.ZCallback64? ZCallback { get; set; } #endif @@ -189,6 +194,11 @@ internal static PointD GetUnitNormal(Point64 pt1, Point64 pt2) return new PointD(dy, -dx); } + public void Execute(DeltaCallback64 deltaCallback, Paths64 solution) + { + DeltaCallback = deltaCallback; + Execute(1.0, solution); + } private static void GetBoundsAndLowestPolyIdx(Paths64 paths, out int index, out Rect64 rec) { @@ -297,8 +307,8 @@ private Point64 GetPerpendic(Point64 pt, PointD norm) return new Point64(pt.X + norm.x * _group_delta, pt.Y + norm.y * _group_delta, pt.Z); #else - return new Point64(pt.X + norm.x * _group_delta, - pt.Y + norm.y * _group_delta); + return new Point64(pt.X + norm.x * _groupDelta, + pt.Y + norm.y * _groupDelta); #endif } @@ -309,8 +319,8 @@ private PointD GetPerpendicD(Point64 pt, PointD norm) return new PointD(pt.X + norm.x * _group_delta, pt.Y + norm.y * _group_delta, pt.Z); #else - return new PointD(pt.X + norm.x * _group_delta, - pt.Y + norm.y * _group_delta); + return new PointD(pt.X + norm.x * _groupDelta, + pt.Y + norm.y * _groupDelta); #endif } @@ -328,22 +338,23 @@ private void DoSquare(Group group, Path64 path, int j, int k) new PointD(-_normals[k].y, _normals[k].x), new PointD(_normals[j].y, -_normals[j].x)); } - + + double absDelta = Math.Abs(_groupDelta); // now offset the original vertex delta units along unit vector PointD ptQ = new PointD(path[j]); - ptQ = TranslatePoint(ptQ, _abs_group_delta * vec.x, _abs_group_delta * vec.y); + ptQ = TranslatePoint(ptQ, absDelta * vec.x, absDelta * vec.y); // get perpendicular vertices - PointD pt1 = TranslatePoint(ptQ, _group_delta * vec.y, _group_delta * -vec.x); - PointD pt2 = TranslatePoint(ptQ, _group_delta * -vec.y, _group_delta * vec.x); + PointD pt1 = TranslatePoint(ptQ, _groupDelta * vec.y, _groupDelta * -vec.x); + PointD pt2 = TranslatePoint(ptQ, _groupDelta * -vec.y, _groupDelta * vec.x); // get 2 vertices along one edge offset PointD pt3 = GetPerpendicD(path[k], _normals[k]); if (j == k) { PointD pt4 = new PointD( - pt3.x + vec.x * _group_delta, - pt3.y + vec.y * _group_delta); + pt3.x + vec.x * _groupDelta, + pt3.y + vec.y * _groupDelta); PointD pt = IntersectPoint(pt1, pt2, pt3, pt4); #if USINGZ pt.z = ptQ.z; @@ -368,7 +379,7 @@ private void DoSquare(Group group, Path64 path, int j, int k) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DoMiter(Group group, Path64 path, int j, int k, double cosA) { - double q = _group_delta / (cosA + 1); + double q = _groupDelta / (cosA + 1); #if USINGZ group.outPath.Add(new Point64( path[j].X + (_normals[k].x + _normals[j].x) * q, @@ -384,8 +395,23 @@ private void DoMiter(Group group, Path64 path, int j, int k, double cosA) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DoRound(Group group, Path64 path, int j, int k, double angle) { + if (DeltaCallback != null) + { + // 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 stepsPer360 = Math.PI / Math.Acos(1 - arcTol / absDelta); + _stepSin = Math.Sin((2 * Math.PI) / stepsPer360); + _stepCos = Math.Cos((2 * Math.PI) / stepsPer360); + if (_groupDelta < 0.0) _stepSin = -_stepSin; + _stepsPerRad = stepsPer360 / (2 * Math.PI); + } + Point64 pt = path[j]; - PointD offsetVec = new PointD(_normals[k].x * _group_delta, _normals[k].y * _group_delta); + PointD offsetVec = new PointD(_normals[k].x * _groupDelta, _normals[k].y * _groupDelta); if (j == k) offsetVec.Negate(); #if USINGZ group.outPath.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y, pt.Z)); @@ -433,8 +459,17 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) if (sinA > 1.0) sinA = 1.0; else if (sinA < -1.0) sinA = -1.0; + if (DeltaCallback != null) + _groupDelta = DeltaCallback(path, _normals, j, k); + if (Math.Abs(_groupDelta) < Tolerance) + { + group.outPath.Add(path[j]); + return; + } - if (cosA > -0.99 && (sinA * _group_delta < 0)) + if (cosA > 0.99) + DoMiter(group, path, j, k, cosA); + else if (cosA > -0.99 && (sinA * _groupDelta < 0)) { // is concave group.outPath.Add(GetPerpendic(path[j], _normals[k])); @@ -449,14 +484,11 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) if (cosA > _mitLimSqr - 1) DoMiter(group, path, j, k, cosA); else DoSquare(group, path, j, k); } - else if (cosA > 0.9998) - // almost straight - less than 1 degree (#424) - DoMiter(group, path, j, k, cosA); - else if (cosA > 0.99 || _joinType == JoinType.Square) + else if (_joinType == JoinType.Square) //angle less than 8 degrees or a squared join DoSquare(group, path, j, k); else - DoRound(group, path, j, k, Math.Atan2(sinA, cosA)); + DoRound(group, path, j, k, Math.Atan2(sinA, cosA)); k = j; } @@ -485,29 +517,35 @@ private void OffsetOpenPath(Group group, Path64 path) group.outPath = new Path64(); int highI = path.Count - 1; + if (DeltaCallback != null) + _groupDelta = DeltaCallback(path, _normals, 0, 0); + // do the line start cap - switch (_endType) - { - case EndType.Butt: + if (Math.Abs(_groupDelta) < Tolerance) + group.outPath.Add(path[0]); + else + switch (_endType) + { + case EndType.Butt: #if USINGZ - group.outPath.Add(new Point64( - path[0].X - _normals[0].x * _group_delta, - path[0].Y - _normals[0].y * _group_delta, - path[0].Z)); + group.outPath.Add(new Point64( + path[0].X - _normals[0].x * _group_delta, + path[0].Y - _normals[0].y * _group_delta, + path[0].Z)); #else - group.outPath.Add(new Point64( - path[0].X - _normals[0].x * _group_delta, - path[0].Y - _normals[0].y * _group_delta)); + group.outPath.Add(new Point64( + path[0].X - _normals[0].x * _groupDelta, + path[0].Y - _normals[0].y * _groupDelta)); #endif - group.outPath.Add(GetPerpendic(path[0], _normals[0])); - break; - case EndType.Round: - DoRound(group, path, 0, 0, Math.PI); - break; - default: - DoSquare(group, path, 0, 0); - break; - } + group.outPath.Add(GetPerpendic(path[0], _normals[0])); + break; + case EndType.Round: + DoRound(group, path, 0, 0, Math.PI); + break; + default: + DoSquare(group, path, 0, 0); + break; + } // offset the left side going forward for (int i = 1, k = 0; i < highI; i++) @@ -518,29 +556,34 @@ private void OffsetOpenPath(Group group, Path64 path) _normals[i] = new PointD(-_normals[i - 1].x, -_normals[i - 1].y); _normals[0] = _normals[highI]; + if (DeltaCallback != null) + _groupDelta = DeltaCallback(path, _normals, highI, highI); // do the line end cap - switch (_endType) - { - case EndType.Butt: + if (Math.Abs(_groupDelta) < Tolerance) + group.outPath.Add(path[highI]); + else + switch (_endType) + { + case EndType.Butt: #if USINGZ - group.outPath.Add(new Point64( - path[highI].X - _normals[highI].x * _group_delta, - path[highI].Y - _normals[highI].y * _group_delta, - path[highI].Z)); + group.outPath.Add(new Point64( + path[highI].X - _normals[highI].x * _group_delta, + path[highI].Y - _normals[highI].y * _group_delta, + path[highI].Z)); #else - group.outPath.Add(new Point64( - path[highI].X - _normals[highI].x * _group_delta, - path[highI].Y - _normals[highI].y * _group_delta)); + group.outPath.Add(new Point64( + path[highI].X - _normals[highI].x * _groupDelta, + path[highI].Y - _normals[highI].y * _groupDelta)); #endif - group.outPath.Add(GetPerpendic(path[highI], _normals[highI])); - break; - case EndType.Round: - DoRound(group, path, highI, highI, Math.PI); - break; - default: - DoSquare(group, path, highI, highI); - break; - } + group.outPath.Add(GetPerpendic(path[highI], _normals[highI])); + break; + case EndType.Round: + DoRound(group, path, highI, highI, Math.PI); + break; + default: + DoSquare(group, path, highI, highI); + break; + } // offset the left side going back for (int i = highI, k = 0; i > 0; i--) @@ -561,32 +604,33 @@ private void DoGroupOffset(Group group) double area = Clipper.Area(group.inPaths[lowestIdx]); //if (area == 0) return; // this is probably unhelpful (#430) group.pathsReversed = (area < 0); - if (group.pathsReversed) _group_delta = -_delta; - else _group_delta = _delta; + if (group.pathsReversed) _groupDelta = -_delta; + else _groupDelta = _delta; } else { group.pathsReversed = false; - _group_delta = Math.Abs(_delta) * 0.5; + _groupDelta = Math.Abs(_delta) * 0.5; } - _abs_group_delta = Math.Abs(_group_delta); + double absDelta = Math.Abs(_groupDelta); _joinType = group.joinType; _endType = group.endType; - // calculate a sensible number of steps (for 360 deg for the given offset - if (group.joinType == JoinType.Round || group.endType == EndType.Round) + if (DeltaCallback == null && + (group.joinType == JoinType.Round || group.endType == EndType.Round)) { + // calculate a sensible number of steps (for 360 deg for the given offset // arcTol - when fArcTolerance is undefined (0), the amount of // curve imprecision that's allowed is based on the size of the // offset (delta). Obviously very large offsets will almost always // require much less precision. See also offset_triginometry2.svg double arcTol = ArcTolerance > 0.01 ? ArcTolerance : - Math.Log10(2 + _abs_group_delta) * InternalClipper.defaultArcTolerance; - double stepsPer360 = Math.PI / Math.Acos(1 - arcTol / _abs_group_delta); + Math.Log10(2 + absDelta) * InternalClipper.defaultArcTolerance; + double stepsPer360 = Math.PI / Math.Acos(1 - arcTol / absDelta); _stepSin = Math.Sin((2 * Math.PI) / stepsPer360); _stepCos = Math.Cos((2 * Math.PI) / stepsPer360); - if (_group_delta < 0.0) _stepSin = -_stepSin; + if (_groupDelta < 0.0) _stepSin = -_stepSin; _stepsPerRad = stepsPer360 / (2 * Math.PI); } @@ -607,7 +651,7 @@ private void DoGroupOffset(Group group) // single vertex so build a circle or square ... if (group.endType == EndType.Round) { - double r = _abs_group_delta; + double r = absDelta; group.outPath = Clipper.Ellipse(path[0], r, r); #if USINGZ group.outPath = InternalClipper.SetZ(group.outPath, path[0].Z); @@ -615,7 +659,7 @@ private void DoGroupOffset(Group group) } else { - int d = (int) Math.Ceiling(_group_delta); + int d = (int) Math.Ceiling(_groupDelta); Rect64 r = new Rect64(path[0].X - d, path[0].Y - d, path[0].X - d, path[0].Y - d); group.outPath = r.AsPath(); diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index cd783e1c..620f9fed 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 1 May 2023 * +* Date : 15 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -269,7 +269,7 @@ TClipperBase = class procedure DoSplitOp(outrec: POutRec; splitOp: POutPt); procedure FixSelfIntersects(outrec: POutRec); function CheckBounds(outrec: POutRec): Boolean; - function CheckSplitOwner(outrec: POutRec): Boolean; + function CheckSplitOwner(outrec: POutRec; const splits: TOutRecArray): Boolean; procedure RecursiveCheckOwners(outrec: POutRec; polytree: TPolyPathBase); protected FUsingPolytree : Boolean; @@ -3022,7 +3022,10 @@ procedure TClipperBase.ProcessHorzJoins; if FUsingPolytree then begin if Path1InsidePath2(or2.pts, or1.pts) then - SetOwner(or2, or1) + begin + SetOwner(or2, or1); + AddSplit(or1, or2); //(#520) + end else if Path1InsidePath2(or1.pts, or2.pts) then SetOwner(or1, or2) else @@ -3694,16 +3697,25 @@ function TClipperBase.CheckBounds(outrec: POutRec): Boolean; end; //------------------------------------------------------------------------------ -function TClipperBase.CheckSplitOwner(outrec: POutRec): Boolean; +function TClipperBase.CheckSplitOwner(outrec: POutRec; const splits: TOutRecArray): Boolean; var i : integer; split : POutrec; begin - for i := 0 to High(outrec.owner.splits) do + Result := false; + for i := 0 to High(splits) do begin - split := GetRealOutRec(outrec.owner.splits[i]); - if Assigned(split) and (split <> outrec) and - (split <> outrec.owner) and CheckBounds(split) and + split := GetRealOutRec(splits[i]); + if not Assigned(split) or + (split = outrec) or (split = outrec.owner) then + Continue + else if Assigned(split.splits) and + CheckSplitOwner(outrec, split.splits) then + begin + Result := True; + Exit; + end + else if CheckBounds(split) and (split.bounds.Contains(outrec.bounds) and Path1InsidePath2(outrec.pts, split.pts)) then begin @@ -3712,7 +3724,6 @@ function TClipperBase.CheckSplitOwner(outrec: POutRec): Boolean; Exit; end; end; - Result := false; end; //------------------------------------------------------------------------------ @@ -3728,7 +3739,7 @@ procedure TClipperBase.RecursiveCheckOwners(outrec: POutRec; polytree: TPolyPath while Assigned(outrec.owner) do begin if Assigned(outrec.owner.splits) and - CheckSplitOwner(outrec) then Break; + CheckSplitOwner(outrec, outrec.owner.splits) then Break; if Assigned(outrec.owner.pts) and CheckBounds(outrec.owner) and (outrec.owner.bounds.Contains(outrec.bounds) and diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index d1d14e06..c2c43c3b 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 8 April 2023 * +* Date : 15 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -26,6 +26,10 @@ interface // etJoined : offsets both sides of a path, with joined ends // etPolygon: offsets only one side of a closed path + TDeltaCallback64 = function (const path: TPath64; + const path_norms: TPathD; currIdx, prevIdx: integer): double of object; + + TGroup = class paths : TPaths64; reversed : Boolean; @@ -38,7 +42,6 @@ TClipperOffset = class private fDelta : Double; fGroupDelta : Double; //*0.5 for open paths; *-1.0 for neg areas - fAbsGrpDelta : Double; fMinLenSqrd : double; fJoinType : TJoinType; fEndType : TEndType; @@ -57,6 +60,7 @@ TClipperOffset = class fSolution : TPaths64; fPreserveCollinear : Boolean; fReverseSolution : Boolean; + fDeltaCallback64 : TDeltaCallback64; {$IFDEF USINGZ} fZCallback64 : TZCallback64; procedure AddPoint(x,y: double; z: Int64); overload; @@ -89,6 +93,7 @@ TClipperOffset = class procedure Clear; procedure Execute(delta: Double; out solution: TPaths64); overload; procedure Execute(delta: Double; polytree: TPolyTree64); overload; + procedure Execute(DeltaCallback: TDeltaCallback64; out solution: TPaths64); overload; // MiterLimit: needed for mitered offsets (see offset_triginometry3.svg) property MiterLimit: Double read fMiterLimit write fMiterLimit; @@ -98,6 +103,8 @@ TClipperOffset = class read fPreserveCollinear write fPreserveCollinear; property ReverseSolution: Boolean read fReverseSolution write fReverseSolution; + property DeltaCallback: TDeltaCallback64 read + fDeltaCallback64 write fDeltaCallback64; {$IFDEF USINGZ} property ZCallback: TZCallback64 read fZCallback64 write fZCallback64; {$ENDIF} @@ -293,6 +300,7 @@ procedure TClipperOffset.DoGroupOffset(group: TGroup); var i,j, len, lowestIdx: Integer; r, stepsPer360, arcTol, area: Double; + absDelta: double; rec: TRect64; isJoined: Boolean; begin @@ -313,24 +321,25 @@ procedure TClipperOffset.DoGroupOffset(group: TGroup); group.reversed := false; fGroupDelta := Abs(fDelta) * 0.5; end; - fAbsGrpDelta := Abs(fGroupDelta); fJoinType := group.joinType; fEndType := group.endType; // calculate a sensible number of steps (for 360 deg for the given offset - if (group.joinType = jtRound) or (group.endType = etRound) then + if (not Assigned(fDeltaCallback64) and + (group.joinType = jtRound) or (group.endType = etRound)) then begin + absDelta := Abs(fGroupDelta); // arcTol - when fArcTolerance is undefined (0), the amount of // curve imprecision that's allowed is based on the size of the // offset (delta). Obviously very large offsets will almost always // require much less precision. See also offset_triginometry2.svg if fArcTolerance > 0.01 then - arcTol := Min(fAbsGrpDelta, fArcTolerance) else - arcTol := Log10(2 + fAbsGrpDelta) * 0.25; // empirically derived + arcTol := Min(absDelta, fArcTolerance) else + arcTol := Log10(2 + absDelta) * 0.25; // empirically derived //http://www.angusj.com/clipper2/Docs/Trigonometry.htm - stepsPer360 := Pi / ArcCos(1 - arcTol / fAbsGrpDelta); - if (stepsPer360 > fAbsGrpDelta * Pi) then - stepsPer360 := fAbsGrpDelta * Pi; // avoid excessive precision + stepsPer360 := Pi / ArcCos(1 - arcTol / absDelta); + if (stepsPer360 > absDelta * Pi) then + stepsPer360 := absDelta * Pi; // avoid excessive precision fStepSin := sin(TwoPi/stepsPer360); fStepCos := cos(TwoPi/stepsPer360); if (fGroupDelta < 0.0) then fStepSin := -fStepSin; @@ -354,9 +363,10 @@ procedure TClipperOffset.DoGroupOffset(group: TGroup); if len = 1 then begin if fGroupDelta < 1 then Continue; + absDelta := Abs(fGroupDelta); if (group.endType = etRound) then begin - r := fAbsGrpDelta; + r := absDelta; with fInPath[0] do begin fOutPath := Path64(Ellipse(RectD(X-r, Y-r, X+r, Y+r))); @@ -367,7 +377,7 @@ procedure TClipperOffset.DoGroupOffset(group: TGroup); end; end else begin - j := Round(fGroupDelta); + j := Round(absDelta); with fInPath[0] do begin rec := Rect64(X -j, Y -j, X+j, Y+j); @@ -450,7 +460,14 @@ procedure TClipperOffset.OffsetOpenPath; begin highI := high(fInPath); - // do the line start cap + if Assigned(fDeltaCallback64) then + fGroupDelta := fDeltaCallback64(fInPath, fNorms, 0, 0); + + // do the line start cap + if Abs(fGroupDelta) < Tolerance then + begin + AddPoint(fInPath[0]); + end else case fEndType of etButt: begin @@ -484,6 +501,13 @@ procedure TClipperOffset.OffsetOpenPath; fNorms[0] := fNorms[highI]; // do the line end cap + + if Assigned(fDeltaCallback64) then + fGroupDelta := fDeltaCallback64(fInPath, fNorms, highI, highI); + if Abs(fGroupDelta) < Tolerance then + begin + AddPoint(fInPath[highI]); + end else case fEndType of etButt: begin @@ -584,6 +608,13 @@ procedure TClipperOffset.Execute(delta: Double; out solution: TPaths64); end; //------------------------------------------------------------------------------ +procedure TClipperOffset.Execute(DeltaCallback: TDeltaCallback64; out solution: TPaths64); +begin + fDeltaCallback64 := DeltaCallback; + Execute(1.0, solution); +end; +//------------------------------------------------------------------------------ + procedure TClipperOffset.Execute(delta: Double; polytree: TPolyTree64); var dummy: TPaths64; @@ -691,6 +722,7 @@ function ReflectPoint(const pt, pivot: TPointD): TPointD; procedure TClipperOffset.DoSquare(j, k: Integer); var vec, pt1,pt2,pt3,pt4, pt,ptQ : TPointD; + absDelta: double; begin if k = j then begin @@ -705,9 +737,10 @@ procedure TClipperOffset.DoSquare(j, k: Integer); PointD(fNorms[j].Y, -fNorms[j].X)); end; + absDelta := Abs(fGroupDelta); // now offset the original vertex delta units along unit vector ptQ := PointD(fInPath[j]); - ptQ := TranslatePoint(ptQ, fAbsGrpDelta * vec.X, fAbsGrpDelta * vec.Y); + ptQ := TranslatePoint(ptQ, absDelta * vec.X, absDelta * vec.Y); // get perpendicular vertices pt1 := TranslatePoint(ptQ, fGroupDelta * vec.Y, fGroupDelta * -vec.X); @@ -767,9 +800,29 @@ procedure TClipperOffset.DoMiter(j, k: Integer; cosA: Double); procedure TClipperOffset.DoRound(j, k: Integer; angle: double); var i, steps: Integer; + absDelta, arcTol, stepsPer360: double; pt: TPoint64; offDist: TPointD; begin + + if Assigned(fDeltaCallback64) then + begin + // when fDeltaCallback64 is assigned, fGroupDelta won't be constant, + // so we'll need to do the following calculations for *every* vertex. + absDelta := Abs(fGroupDelta); + if fArcTolerance > 0.01 then + arcTol := Min(absDelta, fArcTolerance) else + arcTol := Log10(2 + absDelta) * 0.25; // empirically derived + //http://www.angusj.com/clipper2/Docs/Trigonometry.htm + stepsPer360 := Pi / ArcCos(1 - arcTol / absDelta); + if (stepsPer360 > absDelta * Pi) then + stepsPer360 := absDelta * Pi; // avoid excessive precision + fStepSin := sin(TwoPi/stepsPer360); + fStepCos := cos(TwoPi/stepsPer360); + if (fGroupDelta < 0.0) then fStepSin := -fStepSin; + fStepsPerRad := stepsPer360 / TwoPi; + end; + // nb: angles may be negative but this will always be a convex join pt := fInPath[j]; offDist := ScalePoint(fNorms[k], fGroupDelta); @@ -814,6 +867,14 @@ procedure TClipperOffset.OffsetPoint(j: Integer; var k: integer); if (sinA > 1.0) then sinA := 1.0 else if (sinA < -1.0) then sinA := -1.0; + if Assigned(fDeltaCallback64) then + fGroupDelta := fDeltaCallback64(fInPath, fNorms, j, k); + + if Abs(fGroupDelta) <= Tolerance then + begin + AddPoint(fInPath[j]); + Exit; + end; if (cosA > -0.99) and (sinA * fGroupDelta < 0) then begin From 117f885c3ea86e16da116f9e3c394dddbf36e62c Mon Sep 17 00:00:00 2001 From: angusj Date: Tue, 16 May 2023 15:53:45 +1000 Subject: [PATCH 13/95] Updated CI tests --- CPP/Tests/TestOffsets.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CPP/Tests/TestOffsets.cpp b/CPP/Tests/TestOffsets.cpp index 35bed966..f4d258b1 100644 --- a/CPP/Tests/TestOffsets.cpp +++ b/CPP/Tests/TestOffsets.cpp @@ -167,7 +167,7 @@ TEST(Clipper2Tests, TestOffsets4) // see #482 Clipper2Lib::JoinType::Square, Clipper2Lib::EndType::Polygon); std::cout << solution[0].size() << std::endl; - EXPECT_EQ(solution[0].size(), 6); + EXPECT_EQ(solution[0].size(), 5); paths = { { {0, 0}, {20000, 400}, {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} }; @@ -175,7 +175,7 @@ TEST(Clipper2Tests, TestOffsets4) // see #482 Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); std::cout << solution[0].size() << std::endl; - EXPECT_EQ(solution[0].size(), 6); + EXPECT_EQ(solution[0].size(), 5); paths = { { {0, 0}, {20000, 1500}, {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} }; From f2331c4ea9bd9d2c09f9573a8d4157ef9262f5ee Mon Sep 17 00:00:00 2001 From: angusj Date: Tue, 16 May 2023 17:52:32 +1000 Subject: [PATCH 14/95] Minor tweak to PolyTree path ownership --- CPP/Clipper2Lib/src/clipper.engine.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index e29f2fe6..05f70c70 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -419,6 +419,11 @@ namespace Clipper2Lib { return outrec; } + inline bool GetRealSplit(OutRec* split, OutRec* outrec) + { + while (split && split != outrec && !split->pts) split = split->owner; + return split != outrec; + } inline void UncoupleOutRec(Active ae) { @@ -2831,10 +2836,9 @@ namespace Clipper2Lib { bool ClipperBase::CheckSplitOwner(OutRec* outrec, OutRecList* splits) { - for (auto s : *splits) + for (auto split : *splits) { - OutRec* split = GetRealOutRec(s); - if (!split || split == outrec || split == outrec->owner) continue; + if (!GetRealSplit(split, outrec) || !split) continue; else if (split->splits && CheckSplitOwner(outrec, split->splits)) return true; else if (CheckBounds(split) && split->bounds.Contains(outrec->bounds) && Path1InsidePath2(outrec->pts, split->pts)) From 41fc75800dbea8ba02c661849661850bf46aafa4 Mon Sep 17 00:00:00 2001 From: angusj Date: Tue, 16 May 2023 18:42:11 +1000 Subject: [PATCH 15/95] Further tweaks to Polytree path ownership --- CPP/Clipper2Lib/src/clipper.engine.cpp | 9 +++++---- CSharp/Clipper2Lib/Clipper.Engine.cs | 14 +++++++++++--- Delphi/Clipper2Lib/Clipper.Engine.pas | 18 +++++++++++++----- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index 05f70c70..a5f7fb03 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -419,10 +419,11 @@ namespace Clipper2Lib { return outrec; } - inline bool GetRealSplit(OutRec* split, OutRec* outrec) + inline bool GetRealSplit(OutRec*& split, OutRec* outrec) { - while (split && split != outrec && !split->pts) split = split->owner; - return split != outrec; + while (split && !split->pts && split != outrec && split != outrec->owner) + split = split->owner; + return split && split != outrec && split != outrec->owner; } inline void UncoupleOutRec(Active ae) @@ -2838,7 +2839,7 @@ namespace Clipper2Lib { { for (auto split : *splits) { - if (!GetRealSplit(split, outrec) || !split) continue; + if (!GetRealSplit(split, outrec)) continue; else if (split->splits && CheckSplitOwner(outrec, split->splits)) return true; else if (CheckBounds(split) && split->bounds.Contains(outrec->bounds) && Path1InsidePath2(outrec->pts, split->pts)) diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 995ca382..129fb51d 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -723,6 +723,14 @@ private static double AreaTriangle(Point64 pt1, Point64 pt2, Point64 pt3) return outRec; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool GetRealSplit(ref OutRec? split, OutRec outrec) + { + while (split != null && split.pts != null && split != outrec && split != outrec.owner) + split = split.owner; + return split != null && split != outrec && split != outrec.owner; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void UncoupleOutRec(Active ae) { @@ -3014,9 +3022,9 @@ private bool CheckSplitOwner(OutRec outrec, List? splits) { foreach (int i in outrec.owner!.splits!) { - OutRec? split = GetRealOutRec(_outrecList[i]); - if (split == null || split == outrec || split == outrec.owner) continue; - else if (split.splits != null && CheckSplitOwner(outrec, split.splits)) return true; + OutRec? split = _outrecList[i]; + if (!GetRealSplit(ref split, outrec)) continue; + else if (split!.splits != null && CheckSplitOwner(outrec, split.splits)) return true; else if (CheckBounds(split) && split.bounds.Contains(outrec.bounds) && Path1InsidePath2(outrec.pts!, split.pts!)) { diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index 620f9fed..ab2fd658 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 15 May 2023 * +* Date : 16 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -640,6 +640,16 @@ function GetRealOutRec(outRec: POutRec): POutRec; end; //------------------------------------------------------------------------------ +function GetRealSplit(var split: POutRec; outRec: POutRec): Boolean; + {$IFDEF INLINING} inline; {$ENDIF} +begin + while Assigned(split) and not Assigned(split.pts) and + (split <> outrec) and (split <> outrec.owner) do + split := split.owner; + Result := Assigned(split) and (split <> outrec) and (split <> outrec.owner); +end; +//------------------------------------------------------------------------------ + function PtsReallyClose(const pt1, pt2: TPoint64): Boolean; {$IFDEF INLINING} inline; {$ENDIF} begin @@ -3705,10 +3715,8 @@ function TClipperBase.CheckSplitOwner(outrec: POutRec; const splits: TOutRecArra Result := false; for i := 0 to High(splits) do begin - split := GetRealOutRec(splits[i]); - if not Assigned(split) or - (split = outrec) or (split = outrec.owner) then - Continue + split := splits[i]; + if not GetRealSplit(split, outrec) then Continue else if Assigned(split.splits) and CheckSplitOwner(outrec, split.splits) then begin From f3fd5609e802ef72508353e2c057606e99338933 Mon Sep 17 00:00:00 2001 From: angusj Date: Tue, 16 May 2023 19:05:00 +1000 Subject: [PATCH 16/95] one more tweak to Polytree path ownership --- CPP/Clipper2Lib/src/clipper.engine.cpp | 9 +-------- CSharp/Clipper2Lib/Clipper.Engine.cs | 10 +--------- Delphi/Clipper2Lib/Clipper.Engine.pas | 12 +----------- 3 files changed, 3 insertions(+), 28 deletions(-) diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index a5f7fb03..3ba9c603 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -419,13 +419,6 @@ namespace Clipper2Lib { return outrec; } - inline bool GetRealSplit(OutRec*& split, OutRec* outrec) - { - while (split && !split->pts && split != outrec && split != outrec->owner) - split = split->owner; - return split && split != outrec && split != outrec->owner; - } - inline void UncoupleOutRec(Active ae) { OutRec* outrec = ae.outrec; @@ -2839,7 +2832,7 @@ namespace Clipper2Lib { { for (auto split : *splits) { - if (!GetRealSplit(split, outrec)) continue; + if(split == outrec || split == outrec->owner) continue; else if (split->splits && CheckSplitOwner(outrec, split->splits)) return true; else if (CheckBounds(split) && split->bounds.Contains(outrec->bounds) && Path1InsidePath2(outrec->pts, split->pts)) diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 129fb51d..605699b6 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -723,14 +723,6 @@ private static double AreaTriangle(Point64 pt1, Point64 pt2, Point64 pt3) return outRec; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool GetRealSplit(ref OutRec? split, OutRec outrec) - { - while (split != null && split.pts != null && split != outrec && split != outrec.owner) - split = split.owner; - return split != null && split != outrec && split != outrec.owner; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void UncoupleOutRec(Active ae) { @@ -3023,7 +3015,7 @@ private bool CheckSplitOwner(OutRec outrec, List? splits) foreach (int i in outrec.owner!.splits!) { OutRec? split = _outrecList[i]; - if (!GetRealSplit(ref split, outrec)) continue; + if (split == outrec || split == outrec.owner) continue; else if (split!.splits != null && CheckSplitOwner(outrec, split.splits)) return true; else if (CheckBounds(split) && split.bounds.Contains(outrec.bounds) && Path1InsidePath2(outrec.pts!, split.pts!)) diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index ab2fd658..0fab26de 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -640,16 +640,6 @@ function GetRealOutRec(outRec: POutRec): POutRec; end; //------------------------------------------------------------------------------ -function GetRealSplit(var split: POutRec; outRec: POutRec): Boolean; - {$IFDEF INLINING} inline; {$ENDIF} -begin - while Assigned(split) and not Assigned(split.pts) and - (split <> outrec) and (split <> outrec.owner) do - split := split.owner; - Result := Assigned(split) and (split <> outrec) and (split <> outrec.owner); -end; -//------------------------------------------------------------------------------ - function PtsReallyClose(const pt1, pt2: TPoint64): Boolean; {$IFDEF INLINING} inline; {$ENDIF} begin @@ -3716,7 +3706,7 @@ function TClipperBase.CheckSplitOwner(outrec: POutRec; const splits: TOutRecArra for i := 0 to High(splits) do begin split := splits[i]; - if not GetRealSplit(split, outrec) then Continue + if (split = outrec) or (split = outrec.owner) then Continue else if Assigned(split.splits) and CheckSplitOwner(outrec, split.splits) then begin From 9d50a822937c1d098647b72545249f54413ecec8 Mon Sep 17 00:00:00 2001 From: angusj Date: Sat, 20 May 2023 13:17:32 +1000 Subject: [PATCH 17/95] Fixed a minor (C++) bug in open path clipping when using Z (#525) --- CPP/Clipper2Lib/src/clipper.engine.cpp | 20 +++++++------ CPP/Clipper2Lib/src/clipper.offset.cpp | 40 +++++++++++++------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index 3ba9c603..b8837669 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 May 2023 * +* Date : 20 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -1659,17 +1659,14 @@ namespace Clipper2Lib { default: if (std::abs(edge_c->wind_cnt) != 1) return nullptr; break; } + OutPt* resultOp; //toggle contribution ... if (IsHotEdge(*edge_o)) { - OutPt* resultOp = AddOutPt(*edge_o, pt); -#ifdef USINGZ - if (zCallback_) SetZ(e1, e2, resultOp->pt); -#endif + resultOp = AddOutPt(*edge_o, pt); if (IsFront(*edge_o)) edge_o->outrec->front_edge = nullptr; else edge_o->outrec->back_edge = nullptr; edge_o->outrec = nullptr; - return resultOp; } //horizontal edges can pass under open paths at a LocMins @@ -1689,11 +1686,16 @@ namespace Clipper2Lib { return e3->outrec->pts; } else - return StartOpenPath(*edge_o, pt); + resultOp = StartOpenPath(*edge_o, pt); } else - return StartOpenPath(*edge_o, pt); - } + resultOp = StartOpenPath(*edge_o, pt); + +#ifdef USINGZ + if (zCallback_) SetZ(*edge_o, *edge_c, resultOp->pt); +#endif + return resultOp; + } // end of an open path intersection //MANAGING CLOSED PATHS FROM HERE ON diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index 5baa466a..6c46bf42 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 15 May 2023 * +* Date : 17 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -325,8 +325,10 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) return; } - if (cos_a > 0.99) // 0.99 ~= 8.1 deg. + if (cos_a > 0.999) // almost straight - less than 2.5 degree (#424) + { DoMiter(group, path, j, k, cos_a); + } else if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) { // is concave @@ -342,7 +344,7 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a); else DoSquare(group, path, j, k); } - else if (join_type_ == JoinType::Square) + else if (cos_a > 0.99 || join_type_ == JoinType::Square) // 0.99 ~= 8.1 deg. DoSquare(group, path, j, k); else DoRound(group, path, j, k, std::atan2(sin_a, cos_a)); @@ -485,25 +487,23 @@ void ClipperOffset::DoGroupOffset(Group& group) join_type_ = group.join_type; end_type_ = group.end_type; - //calculate a sensible number of steps (for 360 deg for the given offset - if (group.join_type == JoinType::Round || group.end_type == EndType::Round) + if (!deltaCallback64_ && + (group.join_type == JoinType::Round || group.end_type == EndType::Round)) { + //calculate a sensible number of steps (for 360 deg for the given offset) + // arcTol - when arc_tolerance_ is undefined (0), the amount of + // curve imprecision that's allowed is based on the size of the + // offset (delta). Obviously very large offsets will almost always + // require much less precision. See also offset_triginometry2.svg + double arcTol = (arc_tolerance_ > floating_point_tolerance ? + std::min(abs_delta, arc_tolerance_) : + std::log10(2 + abs_delta) * default_arc_tolerance); - if (!deltaCallback64_) { - // arcTol - when arc_tolerance_ is undefined (0), the amount of - // curve imprecision that's allowed is based on the size of the - // offset (delta). Obviously very large offsets will almost always - // require much less precision. See also offset_triginometry2.svg - double arcTol = (arc_tolerance_ > floating_point_tolerance ? - std::min(abs_delta, arc_tolerance_) : - std::log10(2 + abs_delta) * default_arc_tolerance); - - double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI); - step_sin_ = std::sin(2 * PI / steps_per_360); - step_cos_ = std::cos(2 * PI / steps_per_360); - if (group_delta_ < 0.0) step_sin_ = -step_sin_; - steps_per_rad_ = steps_per_360 / (2 * PI); - } + double steps_per_360 = std::min(PI / std::acos(1 - arcTol / abs_delta), abs_delta * PI); + step_sin_ = std::sin(2 * PI / steps_per_360); + step_cos_ = std::cos(2 * PI / steps_per_360); + if (group_delta_ < 0.0) step_sin_ = -step_sin_; + steps_per_rad_ = steps_per_360 / (2 * PI); } bool is_joined = From 3305b617cdf05bbaa3dda324524c8cc472338070 Mon Sep 17 00:00:00 2001 From: philstopford Date: Wed, 24 May 2023 01:51:47 -0500 Subject: [PATCH 18/95] Update Clipper.Offset.cs (#537) --- CSharp/Clipper2Lib/Clipper.Offset.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index 0716acc4..f85f6f2d 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -304,8 +304,8 @@ private static PointD IntersectPoint(PointD pt1a, PointD pt1b, PointD pt2a, Poin private Point64 GetPerpendic(Point64 pt, PointD norm) { #if USINGZ - return new Point64(pt.X + norm.x * _group_delta, - pt.Y + norm.y * _group_delta, pt.Z); + return new Point64(pt.X + norm.x * _groupDelta, + pt.Y + norm.y * _groupDelta, pt.Z); #else return new Point64(pt.X + norm.x * _groupDelta, pt.Y + norm.y * _groupDelta); @@ -316,8 +316,8 @@ private Point64 GetPerpendic(Point64 pt, PointD norm) private PointD GetPerpendicD(Point64 pt, PointD norm) { #if USINGZ - return new PointD(pt.X + norm.x * _group_delta, - pt.Y + norm.y * _group_delta, pt.Z); + return new PointD(pt.X + norm.x * _groupDelta, + pt.Y + norm.y * _groupDelta, pt.Z); #else return new PointD(pt.X + norm.x * _groupDelta, pt.Y + norm.y * _groupDelta); @@ -529,8 +529,8 @@ private void OffsetOpenPath(Group group, Path64 path) case EndType.Butt: #if USINGZ group.outPath.Add(new Point64( - path[0].X - _normals[0].x * _group_delta, - path[0].Y - _normals[0].y * _group_delta, + path[0].X - _normals[0].x * _groupDelta, + path[0].Y - _normals[0].y * _groupDelta, path[0].Z)); #else group.outPath.Add(new Point64( @@ -567,8 +567,8 @@ private void OffsetOpenPath(Group group, Path64 path) case EndType.Butt: #if USINGZ group.outPath.Add(new Point64( - path[highI].X - _normals[highI].x * _group_delta, - path[highI].Y - _normals[highI].y * _group_delta, + path[highI].X - _normals[highI].x * _groupDelta, + path[highI].Y - _normals[highI].y * _groupDelta, path[highI].Z)); #else group.outPath.Add(new Point64( From 6d69b97d2ad41571f473bdf73b90ac276e946236 Mon Sep 17 00:00:00 2001 From: angusj Date: Fri, 26 May 2023 19:41:49 +1000 Subject: [PATCH 19/95] Renamed (again) ExecuteRectClip and ExecuteRectClipLines functions to RectClip and RectClipLines respectively. Fixed minor C# offsetting bugs Fixed RectClipLines bug (C# only) (#530) --- .../include/clipper2/clipper.export.h | 28 ++++---- CPP/Clipper2Lib/include/clipper2/clipper.h | 67 ++++++++++++++----- .../include/clipper2/clipper.rectclip.h | 14 ++-- CPP/Clipper2Lib/src/clipper.offset.cpp | 12 ++-- CPP/Clipper2Lib/src/clipper.rectclip.cpp | 28 ++++---- CPP/Examples/RectClipping/RectClipping.cpp | 10 +-- CPP/Tests/TestRectClip.cpp | 16 ++--- CSharp/Clipper2Lib.Examples/RectClip/Main.cs | 2 +- CSharp/Clipper2Lib/Clipper.Offset.cs | 8 +-- CSharp/Clipper2Lib/Clipper.RectClip.cs | 12 ++-- CSharp/Clipper2Lib/Clipper.cs | 32 ++++----- DLL/Delphi_TestApp/Test_DLL.dpr | 39 +++++------ Delphi/Clipper2Lib/Clipper.Offset.pas | 13 ++-- Delphi/Clipper2Lib/Clipper.RectClip.pas | 40 +++++------ Delphi/Clipper2Lib/Clipper.pas | 50 +++++++------- 15 files changed, 197 insertions(+), 174 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index e8d678a4..438819f2 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 23 March 2023 * +* Date : 26 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module exports the Clipper2 Library (ie DLL/so) * @@ -157,14 +157,14 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, int precision = 2, double miter_limit = 2.0, double arc_tolerance = 0.0, bool reverse_solution = false); -// ExecuteRectClip & ExecuteRectClipLines: -EXTERN_DLL_EXPORT CPaths64 ExecuteRectClip64(const CRect64& rect, +// RectClip & RectClipLines: +EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths, bool convex_only = false); -EXTERN_DLL_EXPORT CPathsD ExecuteRectClipD(const CRectD& rect, +EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int precision = 2, bool convex_only = false); -EXTERN_DLL_EXPORT CPaths64 ExecuteRectClipLines64(const CRect64& rect, +EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, const CPaths64 paths); -EXTERN_DLL_EXPORT CPathsD ExecuteRectClipLinesD(const CRectD& rect, +EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, const CPathsD paths, int precision = 2); ////////////////////////////////////////////////////// @@ -381,18 +381,18 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, return CreateCPathsD(result, 1/scale); } -EXTERN_DLL_EXPORT CPaths64 ExecuteRectClip64(const CRect64& rect, +EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths, bool convex_only) { if (CRectIsEmpty(rect) || !paths) return nullptr; Rect64 r64 = CRectToRect(rect); - class RectClip rc(r64); + class RectClip64 rc(r64); Paths64 pp = ConvertCPaths64(paths); Paths64 result = rc.Execute(pp, convex_only); return CreateCPaths64(result); } -EXTERN_DLL_EXPORT CPathsD ExecuteRectClipD(const CRectD& rect, +EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int precision, bool convex_only) { if (CRectIsEmpty(rect) || !paths) return nullptr; @@ -402,30 +402,30 @@ EXTERN_DLL_EXPORT CPathsD ExecuteRectClipD(const CRectD& rect, RectD r = CRectToRect(rect); Rect64 rec = ScaleRect(r, scale); Paths64 pp = ConvertCPathsD(paths, scale); - class RectClip rc(rec); + class RectClip64 rc(rec); Paths64 result = rc.Execute(pp, convex_only); return CreateCPathsD(result, 1/scale); } -EXTERN_DLL_EXPORT CPaths64 ExecuteRectClipLines64(const CRect64& rect, +EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, const CPaths64 paths) { if (CRectIsEmpty(rect) || !paths) return nullptr; Rect64 r = CRectToRect(rect); - class RectClipLines rcl (r); + class RectClipLines64 rcl (r); Paths64 pp = ConvertCPaths64(paths); Paths64 result = rcl.Execute(pp); return CreateCPaths64(result); } -EXTERN_DLL_EXPORT CPathsD ExecuteRectClipLinesD(const CRectD& rect, +EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, const CPathsD paths, int precision) { if (CRectIsEmpty(rect) || !paths) return nullptr; if (precision < -8 || precision > 8) return nullptr; const double scale = std::pow(10, precision); Rect64 r = ScaleRect(CRectToRect(rect), scale); - class RectClipLines rcl(r); + class RectClipLines64 rcl(r); Paths64 pp = ConvertCPathsD(paths, scale); Paths64 result = rcl.Execute(pp); return CreateCPathsD(result, 1/scale); diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.h b/CPP/Clipper2Lib/include/clipper2/clipper.h index 937e80f7..33bed74a 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 15 May 2023 * +* Date : 26 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -201,23 +201,23 @@ namespace Clipper2Lib { return TranslatePaths(paths, dx, dy); } - inline Paths64 ExecuteRectClip(const Rect64& rect, + inline Paths64 RectClip(const Rect64& rect, const Paths64& paths, bool convex_only) { if (rect.IsEmpty() || paths.empty()) return Paths64(); - RectClip rc(rect); + RectClip64 rc(rect); return rc.Execute(paths, convex_only); } - inline Paths64 ExecuteRectClip(const Rect64& rect, + inline Paths64 RectClip(const Rect64& rect, const Path64& path, bool convex_only) { if (rect.IsEmpty() || path.empty()) return Paths64(); - RectClip rc(rect); + RectClip64 rc(rect); return rc.Execute(Paths64{ path }, convex_only); } - inline PathsD ExecuteRectClip(const RectD& rect, + inline PathsD RectClip(const RectD& rect, const PathsD& paths, bool convex_only, int precision = 2) { if (rect.IsEmpty() || paths.empty()) return PathsD(); @@ -226,32 +226,32 @@ namespace Clipper2Lib { if (error_code) return PathsD(); const double scale = std::pow(10, precision); Rect64 r = ScaleRect(rect, scale); - RectClip rc(r); + RectClip64 rc(r); Paths64 pp = ScalePaths(paths, scale, error_code); if (error_code) return PathsD(); // ie: error_code result is lost return ScalePaths( rc.Execute(pp, convex_only), 1 / scale, error_code); } - inline PathsD ExecuteRectClip(const RectD& rect, + inline PathsD RectClip(const RectD& rect, const PathD& path, bool convex_only, int precision = 2) { - return ExecuteRectClip(rect, PathsD{ path }, convex_only, precision); + return RectClip(rect, PathsD{ path }, convex_only, precision); } - inline Paths64 ExecuteRectClipLines(const Rect64& rect, const Paths64& lines) + inline Paths64 RectClipLines(const Rect64& rect, const Paths64& lines) { if (rect.IsEmpty() || lines.empty()) return Paths64(); - RectClipLines rcl(rect); + RectClipLines64 rcl(rect); return rcl.Execute(lines); } - inline Paths64 ExecuteRectClipLines(const Rect64& rect, const Path64& line) + inline Paths64 RectClipLines(const Rect64& rect, const Path64& line) { - return ExecuteRectClipLines(rect, Paths64{ line }); + return RectClipLines(rect, Paths64{ line }); } - inline PathsD ExecuteRectClipLines(const RectD& rect, const PathsD& lines, int precision = 2) + inline PathsD RectClipLines(const RectD& rect, const PathsD& lines, int precision = 2) { if (rect.IsEmpty() || lines.empty()) return PathsD(); int error_code = 0; @@ -259,16 +259,16 @@ namespace Clipper2Lib { if (error_code) return PathsD(); const double scale = std::pow(10, precision); Rect64 r = ScaleRect(rect, scale); - RectClipLines rcl(r); + RectClipLines64 rcl(r); Paths64 p = ScalePaths(lines, scale, error_code); if (error_code) return PathsD(); p = rcl.Execute(p); return ScalePaths(p, 1 / scale, error_code); } - inline PathsD ExecuteRectClipLines(const RectD& rect, const PathD& line, int precision = 2) + inline PathsD RectClipLines(const RectD& rect, const PathD& line, int precision = 2) { - return ExecuteRectClipLines(rect, PathsD{ line }, precision); + return RectClipLines(rect, PathsD{ line }, precision); } namespace details @@ -489,6 +489,39 @@ namespace Clipper2Lib { return result; } +#ifdef USINGZ + template + inline Path64 MakePathZ(const T2(&list)[N]) + { + static_assert(N % 3 == 0 && std::numeric_limits::is_integer, + "MakePathZ requires integer values in multiples of 3"); + std::size_t size = N / 3; + Path64 result(size); + for (size_t i = 0; i < size; ++i) + result[i] = Point64(list[i * 3], + list[i * 3 + 1], list[i * 3 + 2]); + return result; + } + + template + inline PathD MakePathZD(const T2(&list)[N]) + { + static_assert(N % 3 == 0, + "MakePathZD requires values in multiples of 3"); + std::size_t size = N / 3; + PathD result(size); + if constexpr (std::numeric_limits::is_integer) + for (size_t i = 0; i < size; ++i) + result[i] = PointD(list[i * 3], + list[i * 3 + 1], list[i * 3 + 2]); + else + for (size_t i = 0; i < size; ++i) + result[i] = PointD(list[i * 3], list[i * 3 + 1], + static_cast(list[i * 3 + 2])); + return result; + } +#endif + inline Path64 TrimCollinear(const Path64& p, bool is_open_path = false) { size_t len = p.size(); diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h b/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h index 2a9bb35d..ce769004 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 9 February 2023 * +* Date : 26 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -34,10 +34,10 @@ namespace Clipper2Lib }; //------------------------------------------------------------------------------ - // RectClip + // RectClip64 //------------------------------------------------------------------------------ - class RectClip { + class RectClip64 { private: void ExecuteInternal(const Path64& path); Path64 GetPath(OutPt2*& op); @@ -58,7 +58,7 @@ namespace Clipper2Lib void AddCorner(Location prev, Location curr); void AddCorner(Location& loc, bool isClockwise); public: - explicit RectClip(const Rect64& rect) : + explicit RectClip64(const Rect64& rect) : rect_(rect), rect_as_path_(rect.AsPath()), rect_mp_(rect.MidPoint()) {} @@ -66,15 +66,15 @@ namespace Clipper2Lib }; //------------------------------------------------------------------------------ - // RectClipLines + // RectClipLines64 //------------------------------------------------------------------------------ - class RectClipLines : public RectClip { + class RectClipLines64 : public RectClip64 { private: void ExecuteInternal(const Path64& path); Path64 GetPath(OutPt2*& op); public: - explicit RectClipLines(const Rect64& rect) : RectClip(rect) {}; + explicit RectClipLines64(const Rect64& rect) : RectClip64(rect) {}; Paths64 Execute(const Paths64& paths); }; diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index 6c46bf42..2cefb0ed 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 17 May 2023 * +* Date : 26 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -205,7 +205,7 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t { PointD vec; if (j == k) - vec = PointD(norms[0].y, -norms[0].x); + vec = PointD(norms[j].y, -norms[j].x); else vec = GetAvgUnitVector( PointD(-norms[k].y, norms[k].x), @@ -325,7 +325,7 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) return; } - if (cos_a > 0.999) // almost straight - less than 2.5 degree (#424) + if (cos_a > 0.999) // almost straight - less than 2.5 degree (#424, #526) { DoMiter(group, path, j, k, cos_a); } @@ -334,7 +334,7 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) // is concave group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_)); // this extra point is the only (simple) way to ensure that - // path reversals are fully cleaned with the trailing clipper + // path reversals are fully cleaned with the trailing clipper group.path.push_back(path[j]); // (#405) group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); } @@ -512,7 +512,7 @@ void ClipperOffset::DoGroupOffset(Group& group) Paths64::iterator path_iter; for(path_iter = group.paths_in.begin(); path_iter != group.paths_in.end(); ++path_iter) { - auto path = *path_iter; + Path64 &path = *path_iter; StripDuplicates(path, is_joined); Path64::size_type cnt = path.size(); if (cnt == 0 || ((cnt < 3) && group.end_type == EndType::Polygon)) @@ -618,7 +618,7 @@ void ClipperOffset::Execute(double delta, Paths64& paths) c.Execute(ClipType::Union, FillRule::Negative, paths); else c.Execute(ClipType::Union, FillRule::Positive, paths); -/**/ + /**/ } diff --git a/CPP/Clipper2Lib/src/clipper.rectclip.cpp b/CPP/Clipper2Lib/src/clipper.rectclip.cpp index 54e750f8..e87befe2 100644 --- a/CPP/Clipper2Lib/src/clipper.rectclip.cpp +++ b/CPP/Clipper2Lib/src/clipper.rectclip.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 22 April 2023 * +* Date : 26 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -281,7 +281,7 @@ namespace Clipper2Lib { // RectClip64 //---------------------------------------------------------------------------- - OutPt2* RectClip::Add(Point64 pt, bool start_new) + OutPt2* RectClip64::Add(Point64 pt, bool start_new) { // this method is only called by InternalExecute. // Later splitting & rejoining won't create additional op's, @@ -312,7 +312,7 @@ namespace Clipper2Lib { return result; } - void RectClip::AddCorner(Location prev, Location curr) + void RectClip64::AddCorner(Location prev, Location curr) { if (HeadingClockwise(prev, curr)) Add(rect_as_path_[static_cast(prev)]); @@ -320,7 +320,7 @@ namespace Clipper2Lib { Add(rect_as_path_[static_cast(curr)]); } - void RectClip::AddCorner(Location& loc, bool isClockwise) + void RectClip64::AddCorner(Location& loc, bool isClockwise) { if (isClockwise) { @@ -334,7 +334,7 @@ namespace Clipper2Lib { } } - void RectClip::GetNextLocation(const Path64& path, + void RectClip64::GetNextLocation(const Path64& path, Location& loc, int& i, int highI) { switch (loc) @@ -389,7 +389,7 @@ namespace Clipper2Lib { } //switch } - void RectClip::ExecuteInternal(const Path64& path) + void RectClip64::ExecuteInternal(const Path64& path) { int i = 0, highI = static_cast(path.size()) - 1; Location prev = Location::Inside, loc; @@ -546,7 +546,7 @@ namespace Clipper2Lib { } } - void RectClip::CheckEdges() + void RectClip64::CheckEdges() { for (size_t i = 0; i < results_.size(); ++i) { @@ -606,7 +606,7 @@ namespace Clipper2Lib { } } - void RectClip::TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw) + void RectClip64::TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw) { if (ccw.empty()) return; bool isHorz = ((idx == 1) || (idx == 3)); @@ -784,7 +784,7 @@ namespace Clipper2Lib { } } - Path64 RectClip::GetPath(OutPt2*& op) + Path64 RectClip64::GetPath(OutPt2*& op) { if (!op || op->next == op->prev) return Path64(); @@ -814,7 +814,7 @@ namespace Clipper2Lib { return result; } - Paths64 RectClip::Execute(const Paths64& paths, bool convex_only) + Paths64 RectClip64::Execute(const Paths64& paths, bool convex_only) { Paths64 result; if (rect_.IsEmpty()) return result; @@ -857,10 +857,10 @@ namespace Clipper2Lib { } //------------------------------------------------------------------------------ - // RectClipLines + // RectClipLines64 //------------------------------------------------------------------------------ - Paths64 RectClipLines::Execute(const Paths64& paths) + Paths64 RectClipLines64::Execute(const Paths64& paths) { Paths64 result; if (rect_.IsEmpty()) return result; @@ -886,7 +886,7 @@ namespace Clipper2Lib { return result; } - void RectClipLines::ExecuteInternal(const Path64& path) + void RectClipLines64::ExecuteInternal(const Path64& path) { if (rect_.IsEmpty() || path.size() < 2) return; @@ -956,7 +956,7 @@ namespace Clipper2Lib { /////////////////////////////////////////////////// } - Path64 RectClipLines::GetPath(OutPt2*& op) + Path64 RectClipLines64::GetPath(OutPt2*& op) { Path64 result; if (!op || op == op->next) return result; diff --git a/CPP/Examples/RectClipping/RectClipping.cpp b/CPP/Examples/RectClipping/RectClipping.cpp index 13cc7c2e..6238eb3e 100644 --- a/CPP/Examples/RectClipping/RectClipping.cpp +++ b/CPP/Examples/RectClipping/RectClipping.cpp @@ -57,7 +57,7 @@ void DoEllipses(int cnt) sub.push_back(MakeRandomEllipse(10, 10, 100, 100, width, height)); ////////////////////////////////// - sol = ExecuteRectClip(rect, sub, true); + sol = RectClip(rect, sub, true); ////////////////////////////////// FillRule fr = FillRule::EvenOdd; @@ -93,7 +93,7 @@ void DoRectangles(int cnt) for (int i = 0; i < cnt; ++i) sub.push_back(MakeRandomRectangle(10, 10, 100, 100, width, height)); - sol = ExecuteRectClip(rect, sub, true); + sol = RectClip(rect, sub, true); FillRule fr = FillRule::EvenOdd; SvgWriter svg; @@ -132,7 +132,7 @@ void DoRandomPoly(int count) sub.push_back(MakeRandomPolyD(width, height, count)); ////////////////////////////////// - sol = ExecuteRectClip(rect, sub, false); + sol = RectClip(rect, sub, false); ////////////////////////////////// FillRule fr = FillRule::EvenOdd; @@ -173,12 +173,12 @@ void MeasurePerformance(int min, int max, int step) { Timer t("RectClip: "); - sol = ExecuteRectClip(rect, sub, false); + sol = RectClip(rect, sub, false); } { Timer t("RectClip: (convex flagged)"); - sol = ExecuteRectClip(rect, sub, true); + sol = RectClip(rect, sub, true); } } diff --git a/CPP/Tests/TestRectClip.cpp b/CPP/Tests/TestRectClip.cpp index 1cf2c8c5..5662e9f8 100644 --- a/CPP/Tests/TestRectClip.cpp +++ b/CPP/Tests/TestRectClip.cpp @@ -10,22 +10,22 @@ TEST(Clipper2Tests, TestRectClip) clp.push_back(rect.AsPath()); sub.push_back(MakePath({ 100,100, 700,100, 700,500, 100,500 })); - sol = ExecuteRectClip(rect, sub, false); + sol = RectClip(rect, sub, false); EXPECT_TRUE(Area(sol) == Area(sub)); sub.clear(); sub.push_back(MakePath({ 110,110, 700,100, 700,500, 100,500 })); - sol = ExecuteRectClip(rect, sub, false); + sol = RectClip(rect, sub, false); EXPECT_TRUE(Area(sol) == Area(sub)); sub.clear(); sub.push_back(MakePath({ 90,90, 700,100, 700,500, 100,500 })); - sol = ExecuteRectClip(rect, sub, false); + sol = RectClip(rect, sub, false); EXPECT_TRUE(Area(sol) == Area(clp)); sub.clear(); sub.push_back(MakePath({ 110,110, 690,110, 690,490, 110,490 })); - sol = ExecuteRectClip(rect, sub, false); + sol = RectClip(rect, sub, false); EXPECT_TRUE(Area(sol) == Area(sub)); sub.clear(); @@ -33,24 +33,24 @@ TEST(Clipper2Tests, TestRectClip) rect = Rect64(390, 290, 410, 310); clp.push_back(rect.AsPath()); sub.push_back(MakePath({ 410,290, 500,290, 500,310, 410,310 })); - sol = ExecuteRectClip(rect, sub, false); + sol = RectClip(rect, sub, false); EXPECT_TRUE(sol.empty()); sub.clear(); sub.push_back(MakePath({ 430,290, 470,330, 390,330 })); - sol = ExecuteRectClip(rect, sub, false); + sol = RectClip(rect, sub, false); EXPECT_TRUE(sol.empty()); sub.clear(); sub.push_back(MakePath({ 450,290, 480,330, 450,330 })); - sol = ExecuteRectClip(rect, sub, false); + sol = RectClip(rect, sub, false); EXPECT_TRUE(sol.empty()); sub.clear(); sub.push_back(MakePath({ 208,66, 366,112, 402,303, 234,332, 233,262, 243,140, 215,126, 40,172 })); rect = Rect64(237, 164, 322, 248); - sol = ExecuteRectClip(rect, sub, false); + sol = RectClip(rect, sub, false); const auto solBounds = GetBounds(sol); EXPECT_EQ(solBounds.Width(), rect.Width()); EXPECT_EQ(solBounds.Height(), rect.Height()); diff --git a/CSharp/Clipper2Lib.Examples/RectClip/Main.cs b/CSharp/Clipper2Lib.Examples/RectClip/Main.cs index bbf7a9d9..2ec8426e 100644 --- a/CSharp/Clipper2Lib.Examples/RectClip/Main.cs +++ b/CSharp/Clipper2Lib.Examples/RectClip/Main.cs @@ -83,7 +83,7 @@ public static void DoRandomPoly(bool loadPrevious = false) } ///////////////////////////////////////////////// - sol = Clipper.ExecuteRectClip(rec, sub); + sol = Clipper.RectClip(rec, sub); ///////////////////////////////////////////////// SvgWriter svg = new (FillRule.NonZero); diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index f85f6f2d..635f7a6d 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 May 2023 * +* Date : 26 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -330,7 +330,7 @@ private void DoSquare(Group group, Path64 path, int j, int k) PointD vec; if (j == k) { - vec = new PointD(_normals[0].y, -_normals[0].x); + vec = new PointD(_normals[j].y, -_normals[j].x); } else { @@ -467,7 +467,7 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) return; } - if (cosA > 0.99) + if (cosA > 0.999) DoMiter(group, path, j, k, cosA); else if (cosA > -0.99 && (sinA * _groupDelta < 0)) { @@ -484,7 +484,7 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) if (cosA > _mitLimSqr - 1) DoMiter(group, path, j, k, cosA); else DoSquare(group, path, j, k); } - else if (_joinType == JoinType.Square) + else if (cosA > 0.99 || _joinType == JoinType.Square) //angle less than 8 degrees or a squared join DoSquare(group, path, j, k); else diff --git a/CSharp/Clipper2Lib/Clipper.RectClip.cs b/CSharp/Clipper2Lib/Clipper.RectClip.cs index 6e6354b4..6f62088b 100644 --- a/CSharp/Clipper2Lib/Clipper.RectClip.cs +++ b/CSharp/Clipper2Lib/Clipper.RectClip.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 22 April 2023 * +* Date : 26 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -28,7 +28,7 @@ public OutPt2(Point64 pt) } } - public class RectClip + public class RectClip64 { protected enum Location { @@ -42,7 +42,7 @@ protected enum Location protected List results_; protected List[] edges_; protected int currIdx_ = -1; - internal RectClip(Rect64 rect) + internal RectClip64(Rect64 rect) { currIdx_ = -1; rect_ = rect; @@ -915,9 +915,9 @@ private Path64 GetPath(OutPt2? op) } // RectClip class - public class RectClipLines : RectClip + public class RectClipLines64 : RectClip64 { - internal RectClipLines(Rect64 rect) : base(rect) { } + internal RectClipLines64(Rect64 rect) : base(rect) { } public Paths64 Execute(Paths64 paths) { @@ -1004,7 +1004,7 @@ private void ExecuteInternal(Path64 path) if (loc == Location.inside) // path must be entering rect { - Add(ip); + Add(ip, true); } else if (prev != Location.inside) { diff --git a/CSharp/Clipper2Lib/Clipper.cs b/CSharp/Clipper2Lib/Clipper.cs index 90c97299..cfe61714 100644 --- a/CSharp/Clipper2Lib/Clipper.cs +++ b/CSharp/Clipper2Lib/Clipper.cs @@ -159,24 +159,24 @@ public static PathsD InflatePaths(PathsD paths, double delta, JoinType joinType, return ScalePathsD(tmp, 1 / scale); } - public static Paths64 ExecuteRectClip(Rect64 rect, + public static Paths64 RectClip(Rect64 rect, Paths64 paths, bool convexOnly = false) { if (rect.IsEmpty() || paths.Count == 0) return new Paths64(); - RectClip rc = new RectClip(rect); + RectClip64 rc = new RectClip64(rect); return rc.Execute(paths, convexOnly); } - public static Paths64 ExecuteRectClip(Rect64 rect, + public static Paths64 RectClip(Rect64 rect, Path64 path, bool convexOnly = false) { if (rect.IsEmpty() || path.Count == 0) return new Paths64(); Paths64 tmp = new Paths64(); tmp.Add(path); - return ExecuteRectClip(rect, tmp, convexOnly); + return RectClip(rect, tmp, convexOnly); } - public static PathsD ExecuteRectClip(RectD rect, PathsD paths, + public static PathsD RectClip(RectD rect, PathsD paths, int precision = 2, bool convexOnly = false) { InternalClipper.CheckPrecision(precision); @@ -184,35 +184,35 @@ public static PathsD ExecuteRectClip(RectD rect, PathsD paths, double scale = Math.Pow(10, precision); Rect64 r = ScaleRect(rect, scale); Paths64 tmpPath = ScalePaths64(paths, scale); - RectClip rc = new RectClip(r); + RectClip64 rc = new RectClip64(r); tmpPath = rc.Execute(tmpPath, convexOnly); return ScalePathsD(tmpPath, 1 / scale); } - public static PathsD ExecuteRectClip(RectD rect, PathD path, + public static PathsD RectClip(RectD rect, PathD path, int precision = 2, bool convexOnly = false) { if (rect.IsEmpty() || path.Count == 0) return new PathsD(); PathsD tmp = new PathsD(); tmp.Add(path); - return ExecuteRectClip(rect, tmp, precision, convexOnly); + return RectClip(rect, tmp, precision, convexOnly); } - public static Paths64 ExecuteRectClipLines(Rect64 rect, Paths64 paths) + public static Paths64 RectClipLines(Rect64 rect, Paths64 paths) { if (rect.IsEmpty() || paths.Count == 0) return new Paths64(); - RectClipLines rc = new RectClipLines(rect); + RectClipLines64 rc = new RectClipLines64(rect); return rc.Execute(paths); } - public static Paths64 ExecuteRectClipLines(Rect64 rect, Path64 path) + public static Paths64 RectClipLines(Rect64 rect, Path64 path) { if (rect.IsEmpty() || path.Count == 0) return new Paths64(); Paths64 tmp = new Paths64(); tmp.Add(path); - return ExecuteRectClipLines(rect, tmp); + return RectClipLines(rect, tmp); } - public static PathsD ExecuteRectClipLines(RectD rect, + public static PathsD RectClipLines(RectD rect, PathsD paths, int precision = 2) { InternalClipper.CheckPrecision(precision); @@ -220,16 +220,16 @@ public static PathsD ExecuteRectClipLines(RectD rect, double scale = Math.Pow(10, precision); Rect64 r = ScaleRect(rect, scale); Paths64 tmpPath = ScalePaths64(paths, scale); - RectClipLines rc = new RectClipLines(r); + RectClipLines64 rc = new RectClipLines64(r); tmpPath = rc.Execute(tmpPath); return ScalePathsD(tmpPath, 1 / scale); } - public static PathsD ExecuteRectClipLines(RectD rect, PathD path, int precision = 2) + public static PathsD RectClipLines(RectD rect, PathD path, int precision = 2) { if (rect.IsEmpty() || path.Count == 0) return new PathsD(); PathsD tmp = new PathsD(); tmp.Add(path); - return ExecuteRectClipLines(rect, tmp, precision); + return RectClipLines(rect, tmp, precision); } public static Paths64 MinkowskiSum(Path64 pattern, Path64 path, bool isClosed) { diff --git a/DLL/Delphi_TestApp/Test_DLL.dpr b/DLL/Delphi_TestApp/Test_DLL.dpr index ea936adb..e1076bca 100644 --- a/DLL/Delphi_TestApp/Test_DLL.dpr +++ b/DLL/Delphi_TestApp/Test_DLL.dpr @@ -117,18 +117,18 @@ function InflatePathsD(const paths: CPathsD; reverse_solution: Boolean = false): CPathsD; cdecl; external CLIPPER2_DLL name 'InflatePathsD'; -function ExecuteRectClip64(const rect: TRect64; const paths: CPaths64; +function RectClip64(const rect: TRect64; const paths: CPaths64; convexOnly: Boolean = false): CPaths64; cdecl; - external CLIPPER2_DLL name 'ExecuteRectClip64'; -function ExecuteRectClipD(const rect: TRectD; const paths: CPathsD; + 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 'ExecuteRectClipD'; -function ExecuteRectClipLines64(const rect: TRect64; + external CLIPPER2_DLL name 'RectClipD'; +function RectClipLines64(const rect: TRect64; const paths: CPaths64): CPaths64; cdecl; - external CLIPPER2_DLL name 'ExecuteRectClipLines64'; -function ExecuteRectClipLinesD(const rect: TRectD; + external CLIPPER2_DLL name 'RectClipLines64'; +function RectClipLinesD(const rect: TRectD; const paths: CPathsD; precision: integer = 2): CPathsD; cdecl; - external CLIPPER2_DLL name 'ExecuteRectClipLinesD'; + external CLIPPER2_DLL name 'RectClipLinesD'; //////////////////////////////////////////////////////// // functions related to Clipper2 DLL structures @@ -812,13 +812,12 @@ end; procedure Test_RectClipD(shapeCount: integer); var - i, sol2_len, rec_margin: Integer; + i, rec_margin: Integer; sub, clp, sol1, sol2: TPathsD; csub_local: CPathsD; csol_extern: CPathsD; - scaleRnd, maxOffX, maxOffY, frac: Double; + scaleRnd, maxOffX, maxOffY: Double; rec: TRectD; - fillrule: TFillRule; shapes: array [0..3] of TPathD; const w = 300; @@ -834,8 +833,6 @@ begin shapes[3] := MakePathD([20,60, 20,20, 0,20, 0,0, 60,0, 60,20, 40,20, 40,60]); - fillrule := frNonZero; - // setup WriteLn(#10'Testing RectClip64:'); @@ -857,25 +854,19 @@ begin end; csub_local := TPathsDToCPathsD(sub); - csol_extern := ExecuteRectClipD(rec, csub_local, 2, true); + csol_extern := RectClipD(rec, csub_local, 2, true); sol1 := CPathsDToPathsD(csol_extern); DisposeExportedCPathsD(csol_extern); // do the DLL operation again with ConvexOnly disabled - csol_extern := ExecuteRectClipD(rec, csub_local, 2, false); + csol_extern := RectClipD(rec, csub_local, 2, false); sol2 := CPathsDToPathsD(csol_extern); - // display and clean up - sol2_len := Length(sol2); - if sol2_len = 0 then - frac := 0 else - frac := 1/sol2_len; - SetLength(clp, 1); clp[0] := rec.AsPath; - DisplaySVG(sub, nil, clp, nil, nil, 'RectClip64_1.svg', w,h); - DisplaySVG(sub, nil, clp, sol1, nil, 'RectClip64_2.svg', w,h); + //DisplaySVG(sub, nil, clp, nil, nil, 'RectClip64_1.svg', w,h); + //DisplaySVG(sub, nil, clp, sol1, nil, 'RectClip64_2.svg', w,h); DisplaySVG(sub, nil, clp, sol2, nil, 'RectClip64_3.svg', w,h); DisposeLocalCPathsD(csub_local); @@ -902,7 +893,7 @@ begin rec.Bottom := displayHeight -80; // do the DLL operation - csolo_extern := ExecuteRectClipLines64(rec, csub_local); + csolo_extern := RectClipLines64(rec, csub_local); // optionally display result on the console //WriteCPaths64(csol_extern); diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index c2c43c3b..f111628f 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -398,8 +398,8 @@ procedure TClipperOffset.DoGroupOffset(group: TGroup); fEndType := etRound else fEndType := etSquare; end; - BuildNormals; + if fEndType = etPolygon then OffsetPolygon else if fEndType = etJoined then OffsetOpenJoined else OffsetOpenPath; @@ -726,8 +726,8 @@ procedure TClipperOffset.DoSquare(j, k: Integer); begin if k = j then begin - vec.X := fNorms[0].Y; //squaring a line end - vec.Y := -fNorms[0].X; + vec.X := fNorms[j].Y; //squaring a line end + vec.Y := -fNorms[j].X; end else begin // using the reciprocal of unit normals (as unit vectors) @@ -876,7 +876,9 @@ procedure TClipperOffset.OffsetPoint(j: Integer; var k: integer); Exit; end; - if (cosA > -0.99) and (sinA * fGroupDelta < 0) then + if (cosA > 0.999) then // almost straight - less than 2.5 degree (#424, #526) + DoMiter(j, k, cosA) + else if (cosA > -0.99) and (sinA * fGroupDelta < 0) then begin // is concave AddPoint(GetPerpendic(fInPath[j], fNorms[k], fGroupDelta)); @@ -891,9 +893,6 @@ procedure TClipperOffset.OffsetPoint(j: Integer; var k: integer); if (cosA > fTmpLimit -1) then DoMiter(j, k, cosA) else DoSquare(j, k); end - else if (cosA > 0.9998) then - // almost straight - less than 1 degree (#424) - DoMiter(j, k, cosA) else if (cosA > 0.99) or (fJoinType = jtSquare) then //angle less than 8 degrees or squared joins DoSquare(j, k) diff --git a/Delphi/Clipper2Lib/Clipper.RectClip.pas b/Delphi/Clipper2Lib/Clipper.RectClip.pas index bd581d6d..a8f33070 100644 --- a/Delphi/Clipper2Lib/Clipper.RectClip.pas +++ b/Delphi/Clipper2Lib/Clipper.RectClip.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 22 April 2023 * +* Date : 26 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -32,7 +32,7 @@ TOutPt2 = record prev: POutPt2; end; - TRectClip = class + TRectClip64 = class procedure ExecuteInternal(const path: TPath64); function GetPath(resultIdx: integer): TPath64; protected @@ -61,7 +61,7 @@ TRectClip = class convexOnly: Boolean = false): TPaths64; end; - TRectClipLines = class(TRectClip) + TRectClipLines64 = class(TRectClip64) private procedure ExecuteInternal(const path: TPath64); function GetPath(resultIdx: integer): TPath64; @@ -377,10 +377,10 @@ function IsHeadingClockwise(const pt1, pt2: TPoint64; edgeIdx: integer): Boolean end; //------------------------------------------------------------------------------ -// TRectClip class +// TRectClip64 class //------------------------------------------------------------------------------ -constructor TRectClip.Create(const rect: TRect64); +constructor TRectClip64.Create(const rect: TRect64); begin fResults := TList.Create; @@ -392,7 +392,7 @@ constructor TRectClip.Create(const rect: TRect64); end; //------------------------------------------------------------------------------ -destructor TRectClip.Destroy; +destructor TRectClip64.Destroy; begin fStartLocs.Free; fResults.Free; @@ -414,7 +414,7 @@ procedure DisposeOps(op: POutPt2); end; //------------------------------------------------------------------------------ -procedure TRectClip.DisposeResults; +procedure TRectClip64.DisposeResults; var i: integer; begin @@ -424,7 +424,7 @@ procedure TRectClip.DisposeResults; end; //------------------------------------------------------------------------------ -function TRectClip.Add(const pt: TPoint64; startNewPath: Boolean): POutPt2; +function TRectClip64.Add(const pt: TPoint64; startNewPath: Boolean): POutPt2; var currIdx: integer; prevOp: POutPt2; @@ -462,7 +462,7 @@ function TRectClip.Add(const pt: TPoint64; startNewPath: Boolean): POutPt2; end; //------------------------------------------------------------------------------ -procedure TRectClip.AddCorner(prev, curr: TLocation); +procedure TRectClip64.AddCorner(prev, curr: TLocation); var cnrIdx: integer; begin @@ -474,7 +474,7 @@ procedure TRectClip.AddCorner(prev, curr: TLocation); end; //------------------------------------------------------------------------------ -procedure TRectClip.AddCorner(var loc: TLocation; isClockwise: Boolean); +procedure TRectClip64.AddCorner(var loc: TLocation; isClockwise: Boolean); begin if (isClockwise) then begin @@ -488,7 +488,7 @@ procedure TRectClip.AddCorner(var loc: TLocation; isClockwise: Boolean); end; //------------------------------------------------------------------------------ -procedure TRectClip.GetNextLocation(const path: TPath64; +procedure TRectClip64.GetNextLocation(const path: TPath64; var loc: TLocation; var i: integer; highI: integer); begin case loc of @@ -564,7 +564,7 @@ function Path1ContainsPath2(const path1, path2: TPath64): Boolean; end; //------------------------------------------------------------------------------ -function TRectClip.Execute(const paths: TPaths64; convexOnly: Boolean): TPaths64; +function TRectClip64.Execute(const paths: TPaths64; convexOnly: Boolean): TPaths64; var i,j, len: integer; path: TPath64; @@ -605,7 +605,7 @@ function TRectClip.Execute(const paths: TPaths64; convexOnly: Boolean): TPaths64 end; //------------------------------------------------------------------------------ -procedure TRectClip.ExecuteInternal(const path: TPath64); +procedure TRectClip64.ExecuteInternal(const path: TPath64); var i,highI : integer; prevPt,ip,ip2 : TPoint64; @@ -782,7 +782,7 @@ procedure TRectClip.ExecuteInternal(const path: TPath64); end; //------------------------------------------------------------------------------ -procedure TRectClip.CheckEdges; +procedure TRectClip64.CheckEdges; var i,j: integer; edgeSet1, edgeSet2, combinedSet: Cardinal; @@ -841,7 +841,7 @@ procedure TRectClip.CheckEdges; end; //------------------------------------------------------------------------------ -procedure TRectClip.TidyEdgePair(idx: integer; var cw, ccw: TOutPtArray); +procedure TRectClip64.TidyEdgePair(idx: integer; var cw, ccw: TOutPtArray); var isHorz, cwIsTowardLarger: Boolean; i, j, highJ, newIdx: integer; @@ -1025,7 +1025,7 @@ procedure TRectClip.TidyEdgePair(idx: integer; var cw, ccw: TOutPtArray); end; //------------------------------------------------------------------------------ -function TRectClip.GetPath(resultIdx: integer): TPath64; +function TRectClip64.GetPath(resultIdx: integer): TPath64; var i, len: integer; op, op2: POutPt2; @@ -1057,10 +1057,10 @@ function TRectClip.GetPath(resultIdx: integer): TPath64; end; //------------------------------------------------------------------------------ -// TRectClipLines +// TRectClipLines64 //------------------------------------------------------------------------------ -function TRectClipLines.Execute(const paths: TPaths64): TPaths64; +function TRectClipLines64.Execute(const paths: TPaths64): TPaths64; var i,j, len: integer; pathrec: TRect64; @@ -1089,7 +1089,7 @@ function TRectClipLines.Execute(const paths: TPaths64): TPaths64; end; //------------------------------------------------------------------------------ -procedure TRectClipLines.ExecuteInternal(const path: TPath64); +procedure TRectClipLines64.ExecuteInternal(const path: TPath64); var i, highI : integer; prevPt,ip,ip2 : TPoint64; @@ -1155,7 +1155,7 @@ procedure TRectClipLines.ExecuteInternal(const path: TPath64); end; //------------------------------------------------------------------------------ -function TRectClipLines.GetPath(resultIdx: integer): TPath64; +function TRectClipLines64.GetPath(resultIdx: integer): TPath64; var i, len: integer; op: POutPt2; diff --git a/Delphi/Clipper2Lib/Clipper.pas b/Delphi/Clipper2Lib/Clipper.pas index 626d710e..a924d8b5 100644 --- a/Delphi/Clipper2Lib/Clipper.pas +++ b/Delphi/Clipper2Lib/Clipper.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 21 April 2023 * +* Date : 26 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -97,22 +97,22 @@ function InflatePaths(const paths: TPathsD; delta: Double; // RectClip: for closed paths only (otherwise use RectClipLines) // much faster when only clipping convex polygons -function ExecuteRectClip(const rect: TRect64; const path: TPath64; +function RectClip(const rect: TRect64; const path: TPath64; convexOnly: Boolean = false): TPath64; overload; -function ExecuteRectClip(const rect: TRect64; const paths: TPaths64; +function RectClip(const rect: TRect64; const paths: TPaths64; convexOnly: Boolean = false): TPaths64; overload; -function ExecuteRectClip(const rect: TRectD; const path: TPathD; +function RectClip(const rect: TRectD; const path: TPathD; convexOnly: Boolean = false; precision: integer = 2): TPathD; overload; -function ExecuteRectClip(const rect: TRectD; const paths: TPathsD; +function RectClip(const rect: TRectD; const paths: TPathsD; convexOnly: Boolean = false; precision: integer = 2): TPathsD; overload; -function ExecuteRectClipLines(const rect: TRect64; +function RectClipLines(const rect: TRect64; const path: TPath64): TPaths64; overload; -function ExecuteRectClipLines(const rect: TRect64; +function RectClipLines(const rect: TRect64; const paths: TPaths64): TPaths64; overload; -function ExecuteRectClipLines(const rect: TRectD; const path: TPathD; +function RectClipLines(const rect: TRectD; const path: TPathD; precision: integer = 2): TPathsD; overload; -function ExecuteRectClipLines(const rect: TRectD; const paths: TPathsD; +function RectClipLines(const rect: TRectD; const paths: TPathsD; precision: integer = 2): TPathsD; overload; function TranslatePath(const path: TPath64; dx, dy: Int64): TPath64; overload; @@ -413,26 +413,26 @@ function InflatePaths(const paths: TPathsD; delta: Double; end; //------------------------------------------------------------------------------ -function ExecuteRectClip(const rect: TRect64; +function RectClip(const rect: TRect64; const path: TPath64; convexOnly: Boolean): TPath64; var paths: TPaths64; begin SetLength(paths, 1); paths[0] := path; - paths := ExecuteRectClip(rect, paths, convexOnly); + paths := RectClip(rect, paths, convexOnly); if Assigned(paths) then Result := paths[0] else Result := nil; end; //------------------------------------------------------------------------------ -function ExecuteRectClip(const rect: TRect64; +function RectClip(const rect: TRect64; const paths: TPaths64; convexOnly: Boolean): TPaths64; begin Result := nil; if rect.IsEmpty then Exit; - with TRectClip.Create(rect) do + with TRectClip64.Create(rect) do try Result := Execute(paths, convexOnly); finally @@ -441,7 +441,7 @@ function ExecuteRectClip(const rect: TRect64; end; //------------------------------------------------------------------------------ -function ExecuteRectClip(const rect: TRectD; const path: TPathD; +function RectClip(const rect: TRectD; const path: TPathD; convexOnly: Boolean; precision: integer): TPathD; var scale: double; @@ -454,12 +454,12 @@ function ExecuteRectClip(const rect: TRectD; const path: TPathD; scale := Math.Power(10, precision); rec := Rect64(ScaleRect(rect, scale)); tmpPath := ScalePath(path, scale); - tmpPath := ExecuteRectClip(rec, tmpPath, convexOnly); + tmpPath := RectClip(rec, tmpPath, convexOnly); Result := ScalePathD(tmpPath, 1/scale); end; //------------------------------------------------------------------------------ -function ExecuteRectClip(const rect: TRectD; const paths: TPathsD; +function RectClip(const rect: TRectD; const paths: TPathsD; convexOnly: Boolean; precision: integer): TPathsD; var scale: double; @@ -471,7 +471,7 @@ function ExecuteRectClip(const rect: TRectD; const paths: TPathsD; rec := Rect64(ScaleRect(rect, scale)); tmpPaths := ScalePaths(paths, scale); - with TRectClip.Create(rec) do + with TRectClip64.Create(rec) do try tmpPaths := Execute(tmpPaths); finally @@ -481,14 +481,14 @@ function ExecuteRectClip(const rect: TRectD; const paths: TPathsD; end; //------------------------------------------------------------------------------ -function ExecuteRectClipLines(const rect: TRect64; const path: TPath64): TPaths64; +function RectClipLines(const rect: TRect64; const path: TPath64): TPaths64; var tmp: TPaths64; begin Result := nil; SetLength(tmp, 1); tmp[0] := path; - with TRectClipLines.Create(rect) do + with TRectClipLines64.Create(rect) do try Result := Execute(tmp); finally @@ -497,11 +497,11 @@ function ExecuteRectClipLines(const rect: TRect64; const path: TPath64): TPaths6 end; //------------------------------------------------------------------------------ -function ExecuteRectClipLines(const rect: TRect64; const paths: TPaths64): TPaths64; +function RectClipLines(const rect: TRect64; const paths: TPaths64): TPaths64; begin Result := nil; if rect.IsEmpty then Exit; - with TRectClipLines.Create(rect) do + with TRectClipLines64.Create(rect) do try Result := Execute(paths); finally @@ -510,7 +510,7 @@ function ExecuteRectClipLines(const rect: TRect64; const paths: TPaths64): TPath end; //------------------------------------------------------------------------------ -function ExecuteRectClipLines(const rect: TRectD; +function RectClipLines(const rect: TRectD; const path: TPathD; precision: integer): TPathsD; var scale: double; @@ -524,12 +524,12 @@ function ExecuteRectClipLines(const rect: TRectD; scale := Math.Power(10, precision); rec := Rect64(ScaleRect(rect, scale)); tmpPath := ScalePath(path, scale); - tmpPaths := ExecuteRectClipLines(rec, tmpPath); + tmpPaths := RectClipLines(rec, tmpPath); Result := ScalePathsD(tmpPaths, 1/scale); end; //------------------------------------------------------------------------------ -function ExecuteRectClipLines(const rect: TRectD; const paths: TPathsD; +function RectClipLines(const rect: TRectD; const paths: TPathsD; precision: integer = 2): TPathsD; var scale: double; @@ -542,7 +542,7 @@ function ExecuteRectClipLines(const rect: TRectD; const paths: TPathsD; scale := Math.Power(10, precision); rec := Rect64(ScaleRect(rect, scale)); tmpPaths := ScalePaths(paths, scale); - with TRectClipLines.Create(rec) do + with TRectClipLines64.Create(rec) do try tmpPaths := Execute(tmpPaths); finally From 5aad3190a80dc723aa2168a0651f865bc8d60f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vicente=20Mataix=20Ferr=C3=A1ndiz?= Date: Sat, 27 May 2023 23:57:34 +0200 Subject: [PATCH 20/95] Update clipper.engine.h, missing ` #include ` (#541) --- CPP/Clipper2Lib/include/clipper2/clipper.engine.h | 1 + 1 file changed, 1 insertion(+) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h index 3c049fc5..5cb3b18b 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h @@ -13,6 +13,7 @@ constexpr auto CLIPPER2_VERSION = "1.2.2"; #include +#include #include #include #include From 9d946d748b08b5b361247bd790e5203d99edbfa1 Mon Sep 17 00:00:00 2001 From: angusj Date: Tue, 30 May 2023 08:23:53 +1000 Subject: [PATCH 21/95] Removed ConvexOnly parameter from ClipRect function (#542) --- .../include/clipper2/clipper.export.h | 12 ++++---- CPP/Clipper2Lib/include/clipper2/clipper.h | 20 +++++-------- .../include/clipper2/clipper.rectclip.h | 4 +-- CPP/Clipper2Lib/src/clipper.rectclip.cpp | 15 ++++------ CPP/Examples/RectClipping/RectClipping.cpp | 10 ++----- CPP/Tests/TestRectClip.cpp | 16 +++++----- CSharp/Clipper2Lib/Clipper.RectClip.cs | 13 ++++---- CSharp/Clipper2Lib/Clipper.cs | 22 ++++++-------- Delphi/Clipper2Lib/Clipper.RectClip.pas | 16 ++++------ Delphi/Clipper2Lib/Clipper.pas | 30 +++++++------------ Delphi/Examples/RectClip/RectClip_Demo.dpr | 6 ++-- 11 files changed, 66 insertions(+), 98 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index 438819f2..5bfab9c8 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 May 2023 * +* Date : 30 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module exports the Clipper2 Library (ie DLL/so) * @@ -381,19 +381,17 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, return CreateCPathsD(result, 1/scale); } -EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, - const CPaths64 paths, bool convex_only) +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 = ConvertCPaths64(paths); - Paths64 result = rc.Execute(pp, convex_only); + Paths64 result = rc.Execute(pp); return CreateCPaths64(result); } -EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, - const CPathsD paths, int precision, bool convex_only) +EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int precision) { if (CRectIsEmpty(rect) || !paths) return nullptr; if (precision < -8 || precision > 8) return nullptr; @@ -403,7 +401,7 @@ EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, Rect64 rec = ScaleRect(r, scale); Paths64 pp = ConvertCPathsD(paths, scale); class RectClip64 rc(rec); - Paths64 result = rc.Execute(pp, convex_only); + Paths64 result = rc.Execute(pp); return CreateCPathsD(result, 1/scale); } diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.h b/CPP/Clipper2Lib/include/clipper2/clipper.h index 33bed74a..808f49cc 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.h @@ -201,24 +201,21 @@ namespace Clipper2Lib { return TranslatePaths(paths, dx, dy); } - inline Paths64 RectClip(const Rect64& rect, - const Paths64& paths, bool convex_only) + inline Paths64 RectClip(const Rect64& rect, const Paths64& paths) { if (rect.IsEmpty() || paths.empty()) return Paths64(); RectClip64 rc(rect); - return rc.Execute(paths, convex_only); + return rc.Execute(paths); } - inline Paths64 RectClip(const Rect64& rect, - const Path64& path, bool convex_only) + inline Paths64 RectClip(const Rect64& rect, const Path64& path) { if (rect.IsEmpty() || path.empty()) return Paths64(); RectClip64 rc(rect); - return rc.Execute(Paths64{ path }, convex_only); + return rc.Execute(Paths64{ path }); } - inline PathsD RectClip(const RectD& rect, - const PathsD& paths, bool convex_only, int precision = 2) + inline PathsD RectClip(const RectD& rect, const PathsD& paths, int precision = 2) { if (rect.IsEmpty() || paths.empty()) return PathsD(); int error_code = 0; @@ -230,13 +227,12 @@ namespace Clipper2Lib { Paths64 pp = ScalePaths(paths, scale, error_code); if (error_code) return PathsD(); // ie: error_code result is lost return ScalePaths( - rc.Execute(pp, convex_only), 1 / scale, error_code); + rc.Execute(pp), 1 / scale, error_code); } - inline PathsD RectClip(const RectD& rect, - const PathD& path, bool convex_only, int precision = 2) + inline PathsD RectClip(const RectD& rect, const PathD& path, int precision = 2) { - return RectClip(rect, PathsD{ path }, convex_only, precision); + return RectClip(rect, PathsD{ path }, precision); } inline Paths64 RectClipLines(const Rect64& rect, const Paths64& lines) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h b/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h index ce769004..bcbe7f43 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 May 2023 * +* Date : 30 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -62,7 +62,7 @@ namespace Clipper2Lib rect_(rect), rect_as_path_(rect.AsPath()), rect_mp_(rect.MidPoint()) {} - Paths64 Execute(const Paths64& paths, bool convex_only = false); + Paths64 Execute(const Paths64& paths); }; //------------------------------------------------------------------------------ diff --git a/CPP/Clipper2Lib/src/clipper.rectclip.cpp b/CPP/Clipper2Lib/src/clipper.rectclip.cpp index e87befe2..49cb21ec 100644 --- a/CPP/Clipper2Lib/src/clipper.rectclip.cpp +++ b/CPP/Clipper2Lib/src/clipper.rectclip.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 May 2023 * +* Date : 30 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -814,7 +814,7 @@ namespace Clipper2Lib { return result; } - Paths64 RectClip64::Execute(const Paths64& paths, bool convex_only) + Paths64 RectClip64::Execute(const Paths64& paths) { Paths64 result; if (rect_.IsEmpty()) return result; @@ -833,13 +833,10 @@ namespace Clipper2Lib { } ExecuteInternal(path); - if (!convex_only) - { - CheckEdges(); - for (int i = 0; i < 4; ++i) - TidyEdges(i, edges_[i * 2], edges_[i * 2 + 1]); - } - + CheckEdges(); + for (int i = 0; i < 4; ++i) + TidyEdges(i, edges_[i * 2], edges_[i * 2 + 1]); + for (OutPt2*& op : results_) { Path64 tmp = GetPath(op); diff --git a/CPP/Examples/RectClipping/RectClipping.cpp b/CPP/Examples/RectClipping/RectClipping.cpp index 6238eb3e..b39ebdcd 100644 --- a/CPP/Examples/RectClipping/RectClipping.cpp +++ b/CPP/Examples/RectClipping/RectClipping.cpp @@ -57,7 +57,7 @@ void DoEllipses(int cnt) sub.push_back(MakeRandomEllipse(10, 10, 100, 100, width, height)); ////////////////////////////////// - sol = RectClip(rect, sub, true); + sol = RectClip(rect, sub); ////////////////////////////////// FillRule fr = FillRule::EvenOdd; @@ -93,7 +93,7 @@ void DoRectangles(int cnt) for (int i = 0; i < cnt; ++i) sub.push_back(MakeRandomRectangle(10, 10, 100, 100, width, height)); - sol = RectClip(rect, sub, true); + sol = RectClip(rect, sub); FillRule fr = FillRule::EvenOdd; SvgWriter svg; @@ -173,13 +173,9 @@ void MeasurePerformance(int min, int max, int step) { Timer t("RectClip: "); - sol = RectClip(rect, sub, false); + sol = RectClip(rect, sub); } - { - Timer t("RectClip: (convex flagged)"); - sol = RectClip(rect, sub, true); - } } SvgWriter svg; diff --git a/CPP/Tests/TestRectClip.cpp b/CPP/Tests/TestRectClip.cpp index 5662e9f8..2e8628d2 100644 --- a/CPP/Tests/TestRectClip.cpp +++ b/CPP/Tests/TestRectClip.cpp @@ -10,22 +10,22 @@ TEST(Clipper2Tests, TestRectClip) clp.push_back(rect.AsPath()); sub.push_back(MakePath({ 100,100, 700,100, 700,500, 100,500 })); - sol = RectClip(rect, sub, false); + sol = RectClip(rect, sub); EXPECT_TRUE(Area(sol) == Area(sub)); sub.clear(); sub.push_back(MakePath({ 110,110, 700,100, 700,500, 100,500 })); - sol = RectClip(rect, sub, false); + sol = RectClip(rect, sub); EXPECT_TRUE(Area(sol) == Area(sub)); sub.clear(); sub.push_back(MakePath({ 90,90, 700,100, 700,500, 100,500 })); - sol = RectClip(rect, sub, false); + sol = RectClip(rect, sub); EXPECT_TRUE(Area(sol) == Area(clp)); sub.clear(); sub.push_back(MakePath({ 110,110, 690,110, 690,490, 110,490 })); - sol = RectClip(rect, sub, false); + sol = RectClip(rect, sub); EXPECT_TRUE(Area(sol) == Area(sub)); sub.clear(); @@ -33,24 +33,24 @@ TEST(Clipper2Tests, TestRectClip) rect = Rect64(390, 290, 410, 310); clp.push_back(rect.AsPath()); sub.push_back(MakePath({ 410,290, 500,290, 500,310, 410,310 })); - sol = RectClip(rect, sub, false); + sol = RectClip(rect, sub); EXPECT_TRUE(sol.empty()); sub.clear(); sub.push_back(MakePath({ 430,290, 470,330, 390,330 })); - sol = RectClip(rect, sub, false); + sol = RectClip(rect, sub); EXPECT_TRUE(sol.empty()); sub.clear(); sub.push_back(MakePath({ 450,290, 480,330, 450,330 })); - sol = RectClip(rect, sub, false); + sol = RectClip(rect, sub); EXPECT_TRUE(sol.empty()); sub.clear(); sub.push_back(MakePath({ 208,66, 366,112, 402,303, 234,332, 233,262, 243,140, 215,126, 40,172 })); rect = Rect64(237, 164, 322, 248); - sol = RectClip(rect, sub, false); + sol = RectClip(rect, sub); const auto solBounds = GetBounds(sol); EXPECT_EQ(solBounds.Width(), rect.Width()); EXPECT_EQ(solBounds.Height(), rect.Height()); diff --git a/CSharp/Clipper2Lib/Clipper.RectClip.cs b/CSharp/Clipper2Lib/Clipper.RectClip.cs index 6f62088b..f2b9aa62 100644 --- a/CSharp/Clipper2Lib/Clipper.RectClip.cs +++ b/CSharp/Clipper2Lib/Clipper.RectClip.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 May 2023 * +* Date : 30 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -610,7 +610,7 @@ private void ExecuteInternal(Path64 path) } } - public Paths64 Execute(Paths64 paths, bool convexOnly) + public Paths64 Execute(Paths64 paths) { Paths64 result = new Paths64(); if (rect_.IsEmpty()) return result; @@ -627,12 +627,9 @@ public Paths64 Execute(Paths64 paths, bool convexOnly) continue; } ExecuteInternal(path); - if (!convexOnly) - { - CheckEdges(); - for (int i = 0; i < 4; ++i) - TidyEdgePair(i, edges_[i * 2], edges_[i * 2 + 1]); - } + CheckEdges(); + for (int i = 0; i < 4; ++i) + TidyEdgePair(i, edges_[i * 2], edges_[i * 2 + 1]); foreach (OutPt2? op in results_) { diff --git a/CSharp/Clipper2Lib/Clipper.cs b/CSharp/Clipper2Lib/Clipper.cs index cfe61714..b32778a6 100644 --- a/CSharp/Clipper2Lib/Clipper.cs +++ b/CSharp/Clipper2Lib/Clipper.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 8 April 2023 * +* Date : 30 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module contains simple functions that will likely cover * @@ -159,25 +159,22 @@ public static PathsD InflatePaths(PathsD paths, double delta, JoinType joinType, return ScalePathsD(tmp, 1 / scale); } - public static Paths64 RectClip(Rect64 rect, - Paths64 paths, bool convexOnly = false) + public static Paths64 RectClip(Rect64 rect, Paths64 paths) { if (rect.IsEmpty() || paths.Count == 0) return new Paths64(); RectClip64 rc = new RectClip64(rect); - return rc.Execute(paths, convexOnly); + return rc.Execute(paths); } - public static Paths64 RectClip(Rect64 rect, - Path64 path, bool convexOnly = false) + public static Paths64 RectClip(Rect64 rect, Path64 path) { if (rect.IsEmpty() || path.Count == 0) return new Paths64(); Paths64 tmp = new Paths64(); tmp.Add(path); - return RectClip(rect, tmp, convexOnly); + return RectClip(rect, tmp); } - public static PathsD RectClip(RectD rect, PathsD paths, - int precision = 2, bool convexOnly = false) + public static PathsD RectClip(RectD rect, PathsD paths, int precision = 2) { InternalClipper.CheckPrecision(precision); if (rect.IsEmpty() || paths.Count == 0) return new PathsD(); @@ -185,17 +182,16 @@ public static PathsD RectClip(RectD rect, PathsD paths, Rect64 r = ScaleRect(rect, scale); Paths64 tmpPath = ScalePaths64(paths, scale); RectClip64 rc = new RectClip64(r); - tmpPath = rc.Execute(tmpPath, convexOnly); + tmpPath = rc.Execute(tmpPath); return ScalePathsD(tmpPath, 1 / scale); } - public static PathsD RectClip(RectD rect, PathD path, - int precision = 2, bool convexOnly = false) + public static PathsD RectClip(RectD rect, PathD path, int precision = 2) { if (rect.IsEmpty() || path.Count == 0) return new PathsD(); PathsD tmp = new PathsD(); tmp.Add(path); - return RectClip(rect, tmp, precision, convexOnly); + return RectClip(rect, tmp, precision); } public static Paths64 RectClipLines(Rect64 rect, Paths64 paths) { diff --git a/Delphi/Clipper2Lib/Clipper.RectClip.pas b/Delphi/Clipper2Lib/Clipper.RectClip.pas index a8f33070..3ebc0a78 100644 --- a/Delphi/Clipper2Lib/Clipper.RectClip.pas +++ b/Delphi/Clipper2Lib/Clipper.RectClip.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 26 May 2023 * +* Date : 30 May 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -57,8 +57,7 @@ TRectClip64 = class public constructor Create(const rect: TRect64); destructor Destroy; override; - function Execute(const paths: TPaths64; - convexOnly: Boolean = false): TPaths64; + function Execute(const paths: TPaths64): TPaths64; end; TRectClipLines64 = class(TRectClip64) @@ -564,7 +563,7 @@ function Path1ContainsPath2(const path1, path2: TPath64): Boolean; end; //------------------------------------------------------------------------------ -function TRectClip64.Execute(const paths: TPaths64; convexOnly: Boolean): TPaths64; +function TRectClip64.Execute(const paths: TPaths64): TPaths64; var i,j, len: integer; path: TPath64; @@ -587,12 +586,9 @@ function TRectClip64.Execute(const paths: TPaths64; convexOnly: Boolean): TPaths end; ExecuteInternal(path); - if not convexOnly then - begin - CheckEdges; - for j := 0 to 3 do - TidyEdgePair(j, fEdges[j*2], fEdges[j*2 +1]); - end; + CheckEdges; + for j := 0 to 3 do + TidyEdgePair(j, fEdges[j*2], fEdges[j*2 +1]); for j := 0 to fResults.Count -1 do AppendPath(Result, GetPath(j)); diff --git a/Delphi/Clipper2Lib/Clipper.pas b/Delphi/Clipper2Lib/Clipper.pas index a924d8b5..d54bbaec 100644 --- a/Delphi/Clipper2Lib/Clipper.pas +++ b/Delphi/Clipper2Lib/Clipper.pas @@ -96,15 +96,10 @@ function InflatePaths(const paths: TPathsD; delta: Double; ArcTolerance: double = 0.0): TPathsD; overload; // RectClip: for closed paths only (otherwise use RectClipLines) -// much faster when only clipping convex polygons -function RectClip(const rect: TRect64; const path: TPath64; - convexOnly: Boolean = false): TPath64; overload; -function RectClip(const rect: TRect64; const paths: TPaths64; - convexOnly: Boolean = false): TPaths64; overload; -function RectClip(const rect: TRectD; const path: TPathD; - convexOnly: Boolean = false; precision: integer = 2): TPathD; overload; -function RectClip(const rect: TRectD; const paths: TPathsD; - convexOnly: Boolean = false; precision: integer = 2): TPathsD; overload; +function RectClip(const rect: TRect64; const path: TPath64): TPath64; overload; +function RectClip(const rect: TRect64; const paths: TPaths64): TPaths64; overload; +function RectClip(const rect: TRectD; const path: TPathD; precision: integer = 2): TPathD; overload; +function RectClip(const rect: TRectD; const paths: TPathsD; precision: integer = 2): TPathsD; overload; function RectClipLines(const rect: TRect64; const path: TPath64): TPaths64; overload; @@ -414,35 +409,33 @@ function InflatePaths(const paths: TPathsD; delta: Double; //------------------------------------------------------------------------------ function RectClip(const rect: TRect64; - const path: TPath64; convexOnly: Boolean): TPath64; + const path: TPath64): TPath64; var paths: TPaths64; begin SetLength(paths, 1); paths[0] := path; - paths := RectClip(rect, paths, convexOnly); + paths := RectClip(rect, paths); if Assigned(paths) then Result := paths[0] else Result := nil; end; //------------------------------------------------------------------------------ -function RectClip(const rect: TRect64; - const paths: TPaths64; convexOnly: Boolean): TPaths64; +function RectClip(const rect: TRect64; const paths: TPaths64): TPaths64; begin Result := nil; if rect.IsEmpty then Exit; with TRectClip64.Create(rect) do try - Result := Execute(paths, convexOnly); + Result := Execute(paths); finally Free; end; end; //------------------------------------------------------------------------------ -function RectClip(const rect: TRectD; const path: TPathD; - convexOnly: Boolean; precision: integer): TPathD; +function RectClip(const rect: TRectD; const path: TPathD; precision: integer): TPathD; var scale: double; tmpPath: TPath64; @@ -454,13 +447,12 @@ function RectClip(const rect: TRectD; const path: TPathD; scale := Math.Power(10, precision); rec := Rect64(ScaleRect(rect, scale)); tmpPath := ScalePath(path, scale); - tmpPath := RectClip(rec, tmpPath, convexOnly); + tmpPath := RectClip(rec, tmpPath); Result := ScalePathD(tmpPath, 1/scale); end; //------------------------------------------------------------------------------ -function RectClip(const rect: TRectD; const paths: TPathsD; - convexOnly: Boolean; precision: integer): TPathsD; +function RectClip(const rect: TRectD; const paths: TPathsD; precision: integer): TPathsD; var scale: double; tmpPaths: TPaths64; diff --git a/Delphi/Examples/RectClip/RectClip_Demo.dpr b/Delphi/Examples/RectClip/RectClip_Demo.dpr index 26dbc6b9..aebbe157 100644 --- a/Delphi/Examples/RectClip/RectClip_Demo.dpr +++ b/Delphi/Examples/RectClip/RectClip_Demo.dpr @@ -39,7 +39,7 @@ const rec := Rect64(margin, margin, width - margin, height - margin); clp[0] := rec.AsPath; - sol := ExecuteRectClip(rec, sub); + sol := RectClip(rec, sub); //display with TSvgWriter.Create(fillrule) do @@ -66,7 +66,7 @@ const clp[0] := rec.AsPath; SetLength(sub, 1); sub[0] := PathD(MakeRandomPath(width, height, count)); - sol := ExecuteRectClip(rec, sub); + sol := RectClip(rec, sub); //display with TSvgWriter.Create(fillrule) do @@ -95,7 +95,7 @@ const SetLength(sub, 1); sub[0] := MakeRandomPathD(width, height, lineLength); - sol := ExecuteRectClipLines(rec, sub); + sol := RectClipLines(rec, sub); //display with TSvgWriter.Create(fillrule) do From e6d2ac183a25a24fe1a8aa6c4527ec7bf2b36327 Mon Sep 17 00:00:00 2001 From: angusj Date: Sat, 17 Jun 2023 15:42:59 +1000 Subject: [PATCH 22/95] Fixed infinite loop bug in C# code (#550). --- CPP/Clipper2Lib/src/clipper.engine.cpp | 13 +++++++++---- CSharp/Clipper2Lib/Clipper.Engine.cs | 20 +++++++++++++------- Delphi/Clipper2Lib/Clipper.Engine.pas | 19 ++++++++++--------- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index b8837669..b9c7067c 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 20 May 2023 * +* Date : 17 June 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2126,8 +2126,8 @@ namespace Clipper2Lib { { for (hs2 = hs1 + 1; hs2 != hs_end; ++hs2) { - if (hs2->left_op->pt.x >= hs1->right_op->pt.x) break; - if (hs2->left_to_right == hs1->left_to_right || + if ((hs2->left_op->pt.x >= hs1->right_op->pt.x) || + (hs2->left_to_right == hs1->left_to_right) || (hs2->right_op->pt.x <= hs1->left_op->pt.x)) continue; int64_t curr_y = hs1->left_op->pt.y; if (hs1->left_to_right) @@ -2597,7 +2597,12 @@ namespace Clipper2Lib { ResetHorzDirection(horz, vertex_max, horz_left, horz_right); } - if (IsHotEdge(horz)) AddOutPt(horz, horz.top); + if (IsHotEdge(horz)) + { + OutPt* op = AddOutPt(horz, horz.top); + AddTrialHorzJoin(op); + } + UpdateEdgeIntoAEL(&horz); // end of an intermediate horiz. } diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 605699b6..5fa174da 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 14 May 2023 * +* Date : 17 June 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2269,6 +2269,7 @@ private void DoHorizontal(Active horz) //still more horizontals in bound to process ... if (IsHotEdge(horz)) AddOutPt(horz, horz.top); + UpdateEdgeIntoAEL(horz); if (PreserveCollinear && !horzIsOpen && HorzIsSpike(horz)) @@ -2279,7 +2280,12 @@ private void DoHorizontal(Active horz) } // end for loop and end of (possible consecutive) horizontals - if (IsHotEdge(horz)) AddOutPt(horz, horz.top); + if (IsHotEdge(horz)) + { + OutPt op = AddOutPt(horz, horz.top); + AddToHorzSegList(op); + } + UpdateEdgeIntoAEL(horz); // this is the end of an intermediate horiz. } @@ -2550,8 +2556,8 @@ private void ConvertHorzSegsToJoins() for (int j = i + 1; j < k; j++) { HorzSegment hs2 = _horzSegList[j]; - if (hs2.leftOp!.pt.X >= hs1.rightOp!.pt.X) break; - if (hs2.leftToRight == hs1.leftToRight || + if ((hs2.leftOp!.pt.X >= hs1.rightOp!.pt.X) || + (hs2.leftToRight == hs1.leftToRight) || (hs2.rightOp!.pt.X <= hs1.leftOp!.pt.X)) continue; long curr_y = hs1.leftOp.pt.Y; if ((hs1).leftToRight) @@ -3012,12 +3018,12 @@ private bool CheckBounds(OutRec outrec) private bool CheckSplitOwner(OutRec outrec, List? splits) { - foreach (int i in outrec.owner!.splits!) + foreach (int i in splits!) { OutRec? split = _outrecList[i]; if (split == outrec || split == outrec.owner) continue; - else if (split!.splits != null && CheckSplitOwner(outrec, split.splits)) return true; - else if (CheckBounds(split) && split.bounds.Contains(outrec.bounds) && + if (split!.splits != null && CheckSplitOwner(outrec, split.splits)) return true; + if (CheckBounds(split) && split.bounds.Contains(outrec.bounds) && Path1InsidePath2(outrec.pts!, split.pts!)) { outrec.owner = split; //found in split diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index 0fab26de..166875bb 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 16 May 2023 * +* Date : 17 June 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2438,11 +2438,9 @@ function TClipperBase.IntersectEdges(e1, e2: PActive; pt: TPoint64): POutPt; SetSides(e3.outrec, e3, e1); Result := e3.outrec.pts; Exit; - end - else + end else Result := StartOpenPath(e1, pt); - end - else + end else Result := StartOpenPath(e1, pt); {$IFDEF USINGZ} @@ -2692,7 +2690,7 @@ procedure TClipperBase.ExecuteInternal(clipType: TClipType; FBotY := Y; // FBotY == bottom of current scanbeam if not PopScanLine(Y) then Break; // Y == top of current scanbeam DoIntersections(Y); - DoTopOfScanbeam(Y); + DoTopOfScanbeam(Y); while PopHorz(e) do DoHorizontal(e); end; if Succeeded then ProcessHorzJoins; @@ -2954,8 +2952,8 @@ procedure TClipperBase.ConvertHorzSegsToJoins; begin hs2 := FHorzSegList.UnsafeGet(j); - if hs2.leftOp.pt.X >= hs1.rightOp.pt.X then Break - else if (hs2.leftToRight = hs1.leftToRight) or + if (hs2.leftOp.pt.X >= hs1.rightOp.pt.X) or + (hs2.leftToRight = hs1.leftToRight) or (hs2.rightOp.pt.X <= hs1.leftOp.pt.X) then Continue; currY := hs1.leftOp.pt.Y; @@ -3531,7 +3529,10 @@ procedure TClipperBase.DoHorizontal(horzEdge: PActive); end; // end while horizontal if IsHotEdge(horzEdge) then - AddOutPt(horzEdge, horzEdge.top); + begin + op := AddOutPt(horzEdge, horzEdge.top); + FHorzSegList.Add(op); // Disc.#546 + end; UpdateEdgeIntoAEL(horzEdge); // this is the end of an intermediate horiz. end; From c49dd94b93dc62ed29b0bb7eed94b12119fffadc Mon Sep 17 00:00:00 2001 From: Ondrej Novak <33052757+freza-tm@users.noreply.github.com> Date: Sat, 17 Jun 2023 07:49:48 +0200 Subject: [PATCH 23/95] Use new keyword for hiding base class method (#552) --- CSharp/Clipper2Lib/Clipper.RectClip.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CSharp/Clipper2Lib/Clipper.RectClip.cs b/CSharp/Clipper2Lib/Clipper.RectClip.cs index f2b9aa62..5e3097f9 100644 --- a/CSharp/Clipper2Lib/Clipper.RectClip.cs +++ b/CSharp/Clipper2Lib/Clipper.RectClip.cs @@ -916,7 +916,7 @@ public class RectClipLines64 : RectClip64 { internal RectClipLines64(Rect64 rect) : base(rect) { } - public Paths64 Execute(Paths64 paths) + public new Paths64 Execute(Paths64 paths) { Paths64 result = new Paths64(); if (rect_.IsEmpty()) return result; From 81b01d2acbad7b06fb28df4fbfbd7228519b7905 Mon Sep 17 00:00:00 2001 From: Gornhoth <34295773+Gornhoth@users.noreply.github.com> Date: Tue, 20 Jun 2023 00:45:30 +0200 Subject: [PATCH 24/95] Remove unused convex_only parameter from export rectclip functions (#559) Co-authored-by: Johannes Schatteiner --- CPP/Clipper2Lib/include/clipper2/clipper.export.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index 5bfab9c8..f5f81d2a 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -159,9 +159,9 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, // RectClip & RectClipLines: EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, - const CPaths64 paths, bool convex_only = false); + const CPaths64 paths); EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, - const CPathsD paths, int precision = 2, bool convex_only = false); + const CPathsD paths, int precision = 2); EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, const CPaths64 paths); EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, From 5f1699d14b097fcd924630ea40ce19eaa077822d Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 16 Jul 2023 22:00:50 +1000 Subject: [PATCH 25/95] Minor improvement to the segment intersection algorithm (#568) Fixed significant bugs in Polytree path ownership (#584, D.#576) Fixed a minor bug in RectClip (#586) --- CPP/CMakeLists.txt | 10 +- .../include/clipper2/clipper.core.h | 36 +- .../include/clipper2/clipper.engine.h | 5 +- CPP/Clipper2Lib/include/clipper2/clipper.h | 70 ++-- CPP/Clipper2Lib/src/clipper.engine.cpp | 253 +++++++------- CPP/Clipper2Lib/src/clipper.offset.cpp | 12 +- CPP/Clipper2Lib/src/clipper.rectclip.cpp | 4 +- CPP/Tests/TestLines.cpp | 4 +- CPP/Tests/TestOffsets.cpp | 70 ++-- CPP/Tests/TestPolytreeHoles2.cpp | 8 +- CPP/Utils/Timer.h | 82 ++--- CPP/Utils/clipper.svg.cpp | 14 +- CSharp/Clipper2Lib/Clipper.Core.cs | 20 +- CSharp/Clipper2Lib/Clipper.Engine.cs | 56 +-- CSharp/Clipper2Lib/Clipper.Offset.cs | 5 +- CSharp/Clipper2Lib/Clipper.cs | 2 +- CSharp/Clipper2Lib/Clipper2Lib.csproj | 7 +- CSharp/USINGZ/Clipper2LibZ.csproj | 6 +- CSharp/Utils/ClipFileIO/Clipper.FileIO.csproj | 2 +- CSharp/Utils/Colors/Clipper.Colors.csproj | 4 +- CSharp/Utils/SVG/Clipper2.SVG.csproj | 4 +- Delphi/Clipper2Lib/Clipper.Core.pas | 44 +-- Delphi/Clipper2Lib/Clipper.Engine.pas | 318 +++++++++--------- Delphi/Clipper2Lib/Clipper.Offset.pas | 25 +- Delphi/Clipper2Lib/Clipper.pas | 310 ++++++++++------- 25 files changed, 721 insertions(+), 650 deletions(-) diff --git a/CPP/CMakeLists.txt b/CPP/CMakeLists.txt index ad167700..e3afbc81 100644 --- a/CPP/CMakeLists.txt +++ b/CPP/CMakeLists.txt @@ -2,7 +2,9 @@ cmake_minimum_required(VERSION 3.15) project(Clipper2 VERSION 1.2.2 LANGUAGES C CXX) set(CMAKE_POSITION_INDEPENDENT_CODE ON) -set(CMAKE_CXX_STANDARD 17) +if(NOT DEFINED CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 17) + set(CMAKE_CXX_STANDARD 17) +endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set_property(GLOBAL PROPERTY USE_FOLDERS ON) @@ -35,7 +37,7 @@ set(CLIPPER2_SRC set(CLIPPER2_LIBS "") # one or both of Clipper2/Clipper2Z -# 2d version of Clipper2 +# primary Clipper2 library if (NOT (CLIPPER2_USINGZ STREQUAL "ONLY")) list(APPEND CLIPPER2_LIBS Clipper2) add_library(Clipper2 ${CLIPPER2_INC} ${CLIPPER2_SRC}) @@ -52,7 +54,7 @@ if (NOT (CLIPPER2_USINGZ STREQUAL "ONLY")) endif() endif() -# Clipper2 but with USINGZ defined +# secondary Clipper2 library with USINGZ defined (if required) if (NOT (CLIPPER2_USINGZ STREQUAL "OFF")) list(APPEND CLIPPER2_LIBS Clipper2Z) add_library(Clipper2Z ${CLIPPER2_INC} ${CLIPPER2_SRC}) @@ -207,7 +209,7 @@ endif() target_link_libraries(ClipperTests gtest gtest_main Clipper2 Clipper2utils) gtest_discover_tests(ClipperTests - # set a working directory so your project root so that you can find test data via paths relative to the project root + # set a working directory to your project root so that you can find test data via paths relative to the project root WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/../Tests PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_DIR}" ) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index 9fd53d03..171488f0 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 8 April 2023 * +* Date : 16 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library structures and functions * @@ -53,6 +53,7 @@ namespace Clipper2Lib #ifndef PI static const double PI = 3.141592653589793238; #endif + static const int MAX_DECIMAL_PRECISION = 8; // see https://github.com/AngusJohnson/Clipper2/discussions/564 static const int64_t MAX_COORD = INT64_MAX >> 2; static const int64_t MIN_COORD = -MAX_COORD; static const int64_t INVALID = INT64_MAX; @@ -132,6 +133,7 @@ namespace Clipper2Lib return Point(x * scale, y * scale, z); } + void SetZ(const int64_t z_value) { z = z_value; } friend std::ostream& operator<<(std::ostream& os, const Point& point) { @@ -582,10 +584,10 @@ namespace Clipper2Lib inline void CheckPrecision(int& precision, int& error_code) { - if (precision >= -8 && precision <= 8) return; + if (precision >= -MAX_DECIMAL_PRECISION && precision <= MAX_DECIMAL_PRECISION) return; error_code |= precision_error_i; // non-fatal error - DoError(precision_error_i); // unless exceptions enabled - precision = precision > 8 ? 8 : -8; + DoError(precision_error_i); // does nothing unless exceptions enabled + precision = precision > 0 ? MAX_DECIMAL_PRECISION : -MAX_DECIMAL_PRECISION; } inline void CheckPrecision(int& precision) @@ -677,29 +679,27 @@ namespace Clipper2Lib //nb: This statement is premised on using Cartesian coordinates return Area(poly) >= 0; } - - inline int64_t CheckCastInt64(double val) - { - if ((val >= max_coord) || (val <= min_coord)) return INVALID; - else return static_cast(val); - } - + inline bool GetIntersectPoint(const Point64& ln1a, const Point64& ln1b, const Point64& ln2a, const Point64& ln2b, Point64& ip) { // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection - double dx1 = static_cast(ln1b.x - ln1a.x); double dy1 = static_cast(ln1b.y - ln1a.y); double dx2 = static_cast(ln2b.x - ln2a.x); double dy2 = static_cast(ln2b.y - ln2a.y); + double det = dy1 * dx2 - dy2 * dx1; - if (det == 0.0) return 0; - double qx = dx1 * ln1a.y - dy1 * ln1a.x; - double qy = dx2 * ln2a.y - dy2 * ln2a.x; - ip.x = CheckCastInt64((dx1 * qy - dx2 * qx) / det); - ip.y = CheckCastInt64((dy1 * qy - dy2 * qx) / det); - return (ip.x != INVALID && ip.y != INVALID); + if (det == 0.0) return false; + double t = ((ln1a.x - ln2a.x) * dy2 - (ln1a.y - ln2a.y) * dx2) / det; + if (t <= 0.0) ip = ln1a; // ?? check further (see also #568) + else if (t >= 1.0) ip = ln2a; // ?? check further + else + { + ip.x = static_cast(ln1a.x + t * dx1); + ip.y = static_cast(ln1a.y + t * dy1); + } + return true; } inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b, diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h index 5cb3b18b..91569855 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 15 May 2023 * +* Date : 16 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -13,7 +13,7 @@ constexpr auto CLIPPER2_VERSION = "1.2.2"; #include -#include +#include //#541 #include #include #include @@ -95,7 +95,6 @@ namespace Clipper2Lib { Rect64 bounds = {}; Path64 path; bool is_open = false; - bool horz_done = false; ~OutRec() { if (splits) delete splits; // nb: don't delete the split pointers diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.h b/CPP/Clipper2Lib/include/clipper2/clipper.h index 808f49cc..b5ac6aa9 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 May 2023 * +* Date : 16 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -312,75 +312,55 @@ namespace Clipper2Lib { } static void OutlinePolyPath(std::ostream& os, - bool isHole, size_t count, const std::string& preamble) + size_t idx, bool isHole, size_t count, const std::string& preamble) { std::string plural = (count == 1) ? "." : "s."; if (isHole) - { - if (count) - os << preamble << "+- Hole with " << count << - " nested polygon" << plural << std::endl; - else - os << preamble << "+- Hole" << std::endl; - } + os << preamble << "+- Hole (" << idx << ") contains " << count << + " nested polygon" << plural << std::endl; else - { - if (count) - os << preamble << "+- Polygon with " << count << + os << preamble << "+- Polygon (" << idx << ") contains " << count << " hole" << plural << std::endl; - else - os << preamble << "+- Polygon" << std::endl; - } } static void OutlinePolyPath64(std::ostream& os, const PolyPath64& pp, - std::string preamble, bool last_child) + size_t idx, std::string preamble) { - OutlinePolyPath(os, pp.IsHole(), pp.Count(), preamble); - preamble += (!last_child) ? "| " : " "; - if (pp.Count()) - { - PolyPath64List::const_iterator it = pp.begin(); - for (; it < pp.end() - 1; ++it) - OutlinePolyPath64(os, **it, preamble, false); - OutlinePolyPath64(os, **it, preamble, true); - } + OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble); + for (size_t i = 0; i < pp.Count(); ++i) + if (pp.Child(i)->Count()) + details::OutlinePolyPath64(os, *pp.Child(i), i, preamble + " "); } static void OutlinePolyPathD(std::ostream& os, const PolyPathD& pp, - std::string preamble, bool last_child) + size_t idx, std::string preamble) { - OutlinePolyPath(os, pp.IsHole(), pp.Count(), preamble); - preamble += (!last_child) ? "| " : " "; - if (pp.Count()) - { - PolyPathDList::const_iterator it = pp.begin(); - for (; it < pp.end() - 1; ++it) - OutlinePolyPathD(os, **it, preamble, false); - OutlinePolyPathD(os, **it, preamble, true); - } + OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble); + for (size_t i = 0; i < pp.Count(); ++i) + if (pp.Child(i)->Count()) + details::OutlinePolyPathD(os, *pp.Child(i), i, preamble + " "); } } // end details namespace inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp) { - os << std::endl << "Polytree root" << std::endl; - PolyPath64List::const_iterator it = pp.begin(); - for (; it < pp.end() - 1; ++it) - details::OutlinePolyPath64(os, **it, " ", false); - details::OutlinePolyPath64(os, **it, " ", true); + std::string plural = (pp.Count() == 1) ? " polygon." : " polygons."; + os << std::endl << "Polytree with " << pp.Count() << plural << std::endl; + for (size_t i = 0; i < pp.Count(); ++i) + if (pp.Child(i)->Count()) + details::OutlinePolyPath64(os, *pp.Child(i), i, " "); os << std::endl << std::endl; - if (!pp.Level()) os << std::endl; return os; } inline std::ostream& operator<< (std::ostream& os, const PolyTreeD& pp) { - PolyPathDList::const_iterator it = pp.begin(); - for (; it < pp.end() - 1; ++it) - details::OutlinePolyPathD(os, **it, " ", false); - details::OutlinePolyPathD(os, **it, " ", true); + std::string plural = (pp.Count() == 1) ? " polygon." : " polygons."; + os << std::endl << "Polytree with " << pp.Count() << plural << std::endl; + for (size_t i = 0; i < pp.Count(); ++i) + if (pp.Child(i)->Count()) + details::OutlinePolyPathD(os, *pp.Child(i), i, " "); os << std::endl << std::endl; if (!pp.Level()) os << std::endl; return os; diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index b9c7067c..d89fa3fc 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 17 June 2023 * +* Date : 16 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -483,6 +483,105 @@ namespace Clipper2Lib { outrec->owner = new_owner; } + static PointInPolygonResult PointInOpPolygon(const Point64& pt, OutPt* op) + { + if (op == op->next || op->prev == op->next) + return PointInPolygonResult::IsOutside; + + OutPt* op2 = op; + do + { + if (op->pt.y != pt.y) break; + op = op->next; + } while (op != op2); + if (op->pt.y == pt.y) // not a proper polygon + return PointInPolygonResult::IsOutside; + + bool is_above = op->pt.y < pt.y, starting_above = is_above; + int val = 0; + op2 = op->next; + while (op2 != op) + { + if (is_above) + while (op2 != op && op2->pt.y < pt.y) op2 = op2->next; + else + while (op2 != op && op2->pt.y > pt.y) op2 = op2->next; + if (op2 == op) break; + + // must have touched or crossed the pt.Y horizonal + // and this must happen an even number of times + + if (op2->pt.y == pt.y) // touching the horizontal + { + if (op2->pt.x == pt.x || (op2->pt.y == op2->prev->pt.y && + (pt.x < op2->prev->pt.x) != (pt.x < op2->pt.x))) + return PointInPolygonResult::IsOn; + + op2 = op2->next; + if (op2 == op) break; + continue; + } + + if (pt.x < op2->pt.x && pt.x < op2->prev->pt.x); + // do nothing because + // we're only interested in edges crossing on the left + else if ((pt.x > op2->prev->pt.x && pt.x > op2->pt.x)) + val = 1 - val; // toggle val + else + { + double d = CrossProduct(op2->prev->pt, op2->pt, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + is_above = !is_above; + op2 = op2->next; + } + + if (is_above != starting_above) + { + double d = CrossProduct(op2->prev->pt, op2->pt, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + + if (val == 0) return PointInPolygonResult::IsOutside; + else return PointInPolygonResult::IsInside; + } + + inline Rect64 GetBounds(OutPt* op) + { + Rect64 result(op->pt.x, op->pt.y, op->pt.x, op->pt.y); + OutPt* op2 = op->next; + while (op2 != op) + { + if (op2->pt.x < result.left) result.left = op2->pt.x; + else if (op2->pt.x > result.right) result.right = op2->pt.x; + if (op2->pt.y < result.top) result.top = op2->pt.y; + else if (op2->pt.y > result.bottom) result.bottom = op2->pt.y; + op2 = op2->next; + } + return result; + } + + inline bool Path1InsidePath2(OutPt* op1, OutPt* op2) + { + // we need to make some accommodation for rounding errors + // so we won't jump if the first vertex is found outside + int outside_cnt = 0; + OutPt* op = op1; + do + { + PointInPolygonResult result = PointInOpPolygon(op->pt, op2); + if (result == PointInPolygonResult::IsOutside) ++outside_cnt; + else if (result == PointInPolygonResult::IsInside) --outside_cnt; + op = op->next; + } while (op != op1 && std::abs(outside_cnt) < 2); + if (std::abs(outside_cnt) > 1) return (outside_cnt < 0); + // since path1's location is still equivocal, check its midpoint + Point64 mp = GetBounds(op).MidPoint(); + return PointInOpPolygon(mp, op2) == PointInPolygonResult::IsInside; + } + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ @@ -600,8 +699,6 @@ namespace Clipper2Lib { vertexLists.emplace_back(vertices); } - - //------------------------------------------------------------------------------ // ReuseableDataContainer64 methods ... //------------------------------------------------------------------------------ @@ -1298,7 +1395,7 @@ namespace Clipper2Lib { else SetOwner(&outrec, e->outrec); // nb: outRec.owner here is likely NOT the real - // owner but this will be checked in DeepCheckOwner() + // owner but this will be checked in RecursiveCheckOwners() } UncoupleOutRec(e1); @@ -1472,11 +1569,6 @@ namespace Clipper2Lib { return; } - // nb: area1 is the path's area *before* splitting, whereas area2 is - // the area of the triangle containing splitOp & splitOp.next. - // So the only way for these areas to have the same sign is if - // the split triangle is larger than the path containing prevOp or - // if there's more than one self=intersection. double area2 = AreaTriangle(ip, splitOp->pt, splitOp->next->pt); double absArea2 = std::fabs(area2); @@ -1496,18 +1588,17 @@ namespace Clipper2Lib { prevOp->next = newOp2; } + // area1 is the path's area *before* splitting, whereas area2 is + // the area of the triangle containing splitOp & splitOp.next. + // So the only way for these areas to have the same sign is if + // the split triangle is larger than the path containing prevOp or + // if there's more than one self-intersection. if (absArea2 >= 1 && (absArea2 > absArea1 || (area2 > 0) == (area1 > 0))) { OutRec* newOr = NewOutRec(); newOr->owner = outrec->owner; - if (using_polytree_) - { - if (!outrec->splits) outrec->splits = new OutRecList(); - outrec->splits->push_back(newOr); - } - splitOp->outrec = newOr; splitOp->next->outrec = newOr; OutPt* newOp = new OutPt(ip, newOr); @@ -1516,6 +1607,20 @@ namespace Clipper2Lib { newOr->pts = newOp; splitOp->prev = newOp; splitOp->next->next = newOp; + + if (using_polytree_) + { + if (Path1InsidePath2(prevOp, newOp)) + { + newOr->splits = new OutRecList(); + newOr->splits->push_back(outrec); + } + else + { + if (!outrec->splits) outrec->splits = new OutRecList(); + outrec->splits->push_back(newOr); + } + } } else { @@ -1960,105 +2065,6 @@ namespace Clipper2Lib { } while (op != outrec->pts); } - inline Rect64 GetBounds(OutPt* op) - { - Rect64 result(op->pt.x, op->pt.y, op->pt.x, op->pt.y); - OutPt* op2 = op->next; - while (op2 != op) - { - if (op2->pt.x < result.left) result.left = op2->pt.x; - else if (op2->pt.x > result.right) result.right = op2->pt.x; - if (op2->pt.y < result.top) result.top = op2->pt.y; - else if (op2->pt.y > result.bottom) result.bottom = op2->pt.y; - op2 = op2->next; - } - return result; - } - - static PointInPolygonResult PointInOpPolygon(const Point64& pt, OutPt* op) - { - if (op == op->next || op->prev == op->next) - return PointInPolygonResult::IsOutside; - - OutPt* op2 = op; - do - { - if (op->pt.y != pt.y) break; - op = op->next; - } while (op != op2); - if (op->pt.y == pt.y) // not a proper polygon - return PointInPolygonResult::IsOutside; - - bool is_above = op->pt.y < pt.y, starting_above = is_above; - int val = 0; - op2 = op->next; - while (op2 != op) - { - if (is_above) - while (op2 != op && op2->pt.y < pt.y) op2 = op2->next; - else - while (op2 != op && op2->pt.y > pt.y) op2 = op2->next; - if (op2 == op) break; - - // must have touched or crossed the pt.Y horizonal - // and this must happen an even number of times - - if (op2->pt.y == pt.y) // touching the horizontal - { - if (op2->pt.x == pt.x || (op2->pt.y == op2->prev->pt.y && - (pt.x < op2->prev->pt.x) != (pt.x < op2->pt.x))) - return PointInPolygonResult::IsOn; - - op2 = op2->next; - if (op2 == op) break; - continue; - } - - if (pt.x < op2->pt.x && pt.x < op2->prev->pt.x); - // do nothing because - // we're only interested in edges crossing on the left - else if ((pt.x > op2->prev->pt.x && pt.x > op2->pt.x)) - val = 1 - val; // toggle val - else - { - double d = CrossProduct(op2->prev->pt, op2->pt, pt); - if (d == 0) return PointInPolygonResult::IsOn; - if ((d < 0) == is_above) val = 1 - val; - } - is_above = !is_above; - op2 = op2->next; - } - - if (is_above != starting_above) - { - double d = CrossProduct(op2->prev->pt, op2->pt, pt); - if (d == 0) return PointInPolygonResult::IsOn; - if ((d < 0) == is_above) val = 1 - val; - } - - if (val == 0) return PointInPolygonResult::IsOutside; - else return PointInPolygonResult::IsInside; - } - - inline bool Path1InsidePath2(OutPt* op1, OutPt* op2) - { - // we need to make some accommodation for rounding errors - // so we won't jump if the first vertex is found outside - int outside_cnt = 0; - OutPt* op = op1; - do - { - PointInPolygonResult result = PointInOpPolygon(op->pt, op2); - if (result == PointInPolygonResult::IsOutside) ++outside_cnt; - else if (result == PointInPolygonResult::IsInside) --outside_cnt; - op = op->next; - } while (op != op1 && std::abs(outside_cnt) < 2); - if (std::abs(outside_cnt) > 1) return (outside_cnt < 0); - // since path1's location is still equivocal, check its midpoint - Point64 mp = GetBounds(op).MidPoint(); - return PointInOpPolygon(mp, op2) == PointInPolygonResult::IsInside; - } - inline bool SetHorzSegHeadingForward(HorzSegment& hs, OutPt* opP, OutPt* opN) { if (opP->pt.x == opN->pt.x) return false; @@ -2174,7 +2180,7 @@ namespace Clipper2Lib { op1b->prev = op2b; op2b->next = op1b; - if (or1 == or2) + if (or1 == or2) // 'join' is really a split { or2 = NewOutRec(); or2->pts = op1b; @@ -2185,21 +2191,18 @@ namespace Clipper2Lib { or1->pts->outrec = or1; } - if (using_polytree_) + if (using_polytree_) //#498, #520, #584, D#576 { - if (Path1InsidePath2(or2->pts, or1->pts)) + if (Path1InsidePath2(or1->pts, or2->pts)) { - SetOwner(or2, or1); - if (!or1->splits) or1->splits = new OutRecList(); - or1->splits->push_back(or2); //(#520) - } - else if (Path1InsidePath2(or1->pts, or2->pts)) + or2->owner = or1->owner; SetOwner(or1, or2); + } else { - if (!or1->splits) or1->splits = new OutRecList(); - or1->splits->push_back(or2); //(#498) - or2->owner = or1; + SetOwner(or2, or1); + if (!or1->splits) or1->splits = new OutRecList(); + or1->splits->push_back(or2); } } else diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index 2cefb0ed..017976c7 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 May 2023 * +* Date : 16 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -78,8 +78,7 @@ inline double Hypot(double x, double y) } inline PointD NormalizeVector(const PointD& vec) -{ - +{ double h = Hypot(vec.x, vec.y); if (AlmostZero(h)) return PointD(0,0); double inverseHypot = 1 / h; @@ -318,7 +317,10 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) if (sin_a > 1.0) sin_a = 1.0; else if (sin_a < -1.0) sin_a = -1.0; - if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, j, k); + if (deltaCallback64_) { + group_delta_ = deltaCallback64_(path, norms, j, k); + if (group.is_reversed) group_delta_ = -group_delta_; + } if (std::fabs(group_delta_) <= floating_point_tolerance) { group.path.push_back(path[j]); @@ -602,7 +604,6 @@ void ClipperOffset::Execute(double delta, Paths64& paths) if (!solution.size()) return; paths = solution; - /**/ //clean up self-intersections ... Clipper64 c; c.PreserveCollinear = false; @@ -618,7 +619,6 @@ void ClipperOffset::Execute(double delta, Paths64& paths) c.Execute(ClipType::Union, FillRule::Negative, paths); else c.Execute(ClipType::Union, FillRule::Positive, paths); - /**/ } diff --git a/CPP/Clipper2Lib/src/clipper.rectclip.cpp b/CPP/Clipper2Lib/src/clipper.rectclip.cpp index 49cb21ec..1fad1899 100644 --- a/CPP/Clipper2Lib/src/clipper.rectclip.cpp +++ b/CPP/Clipper2Lib/src/clipper.rectclip.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 30 May 2023 * +* Date : 16 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -847,7 +847,7 @@ namespace Clipper2Lib { //clean up after every loop op_container_ = std::deque(); results_.clear(); - for (OutPt2List edge : edges_) edge.clear(); + for (OutPt2List &edge : edges_) edge.clear(); start_locs_.clear(); } return result; diff --git a/CPP/Tests/TestLines.cpp b/CPP/Tests/TestLines.cpp index 1122a425..def6465b 100644 --- a/CPP/Tests/TestLines.cpp +++ b/CPP/Tests/TestLines.cpp @@ -5,8 +5,7 @@ TEST(Clipper2Tests, TestMultipleLines) { std::ifstream ifs("Lines.txt"); - - + //if (!ifs.good()) return; ASSERT_TRUE(ifs); ASSERT_TRUE(ifs.good()); @@ -61,5 +60,6 @@ TEST(Clipper2Tests, TestMultipleLines) { } ++test_number; } + ifs.close(); EXPECT_GE(test_number, 17); } \ No newline at end of file diff --git a/CPP/Tests/TestOffsets.cpp b/CPP/Tests/TestOffsets.cpp index f4d258b1..984de03c 100644 --- a/CPP/Tests/TestOffsets.cpp +++ b/CPP/Tests/TestOffsets.cpp @@ -3,31 +3,32 @@ #include "ClipFileLoad.h" #include +using namespace Clipper2Lib; TEST(Clipper2Tests, TestOffsets) { - std::ifstream ifs("Offsets.txt"); + if(!ifs.good()) return; for (int test_number = 1; test_number <= 2; ++test_number) { - Clipper2Lib::ClipperOffset co; + ClipperOffset co; - Clipper2Lib::Paths64 subject, subject_open, clip; - Clipper2Lib::Paths64 solution, solution_open; - Clipper2Lib::ClipType ct = Clipper2Lib::ClipType::None; - Clipper2Lib::FillRule fr = Clipper2Lib::FillRule::NonZero; + Paths64 subject, subject_open, clip; + Paths64 solution, solution_open; + ClipType ct = ClipType::None; + 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)); - co.AddPaths(subject, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); - Clipper2Lib::Paths64 outputs; + co.AddPaths(subject, JoinType::Round, EndType::Polygon); + Paths64 outputs; co.Execute(1, outputs); // is the sum total area of the solution is positive - const auto outer_is_positive = Clipper2Lib::Area(outputs) > 0; + const auto outer_is_positive = Area(outputs) > 0; // there should be exactly one exterior path - const auto is_positive_func = Clipper2Lib::IsPositive; + const auto is_positive_func = IsPositive; const auto is_positive_count = std::count_if( outputs.begin(), outputs.end(), is_positive_func); const auto is_negative_count = @@ -37,12 +38,13 @@ TEST(Clipper2Tests, TestOffsets) { else EXPECT_EQ(is_negative_count, 1); } + ifs.close(); } -Clipper2Lib::Point64 MidPoint(const Clipper2Lib::Point64& p1, const Clipper2Lib::Point64& p2) +Point64 MidPoint(const Point64& p1, const Point64& p2) { - Clipper2Lib::Point64 result; + Point64 result; result.x = (p1.x + p2.x) / 2; result.y = (p1.y + p2.y) / 2; return result; @@ -52,25 +54,25 @@ TEST(Clipper2Tests, TestOffsets2) { // see #448 & #456 double scale = 10, delta = 10 * scale, arc_tol = 0.25 * scale; - Clipper2Lib::Paths64 subject, solution; - Clipper2Lib::ClipperOffset c; - subject.push_back(Clipper2Lib::MakePath({ 50,50, 100,50, 100,150, 50,150, 0,100 })); + Paths64 subject, solution; + ClipperOffset c; + subject.push_back(MakePath({ 50,50, 100,50, 100,150, 50,150, 0,100 })); int err; - subject = Clipper2Lib::ScalePaths(subject, scale, err); + subject = ScalePaths(subject, scale, err); - c.AddPaths(subject, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); + c.AddPaths(subject, JoinType::Round, EndType::Polygon); c.ArcTolerance(arc_tol); c.Execute(delta, solution); double min_dist = delta * 2, max_dist = 0; - for (auto subjPt : subject[0]) + for (const Point64& subjPt : subject[0]) { - Clipper2Lib::Point64 prevPt = solution[0][solution[0].size() - 1]; - for (auto pt : solution[0]) + Point64 prevPt = solution[0][solution[0].size() - 1]; + for (const Point64& pt : solution[0]) { - Clipper2Lib::Point64 mp = MidPoint(prevPt, pt); - double d = Clipper2Lib::Distance(mp, subjPt); + Point64 mp = MidPoint(prevPt, pt); + double d = Distance(mp, subjPt); if (d < delta * 2) { if (d < min_dist) min_dist = d; @@ -87,7 +89,7 @@ TEST(Clipper2Tests, TestOffsets2) { // see #448 & #456 TEST(Clipper2Tests, TestOffsets3) // see #424 { - Clipper2Lib::Paths64 subjects = {{ + Paths64 subjects = {{ {1525311078, 1352369439}, {1526632284, 1366692987}, {1519397110, 1367437476}, @@ -145,42 +147,42 @@ TEST(Clipper2Tests, TestOffsets3) // see #424 {1596698132, 1348993024}, {1595775386, 1342722540} }}; - Clipper2Lib::Paths64 solution = InflatePaths(subjects, -209715, - Clipper2Lib::JoinType::Miter, Clipper2Lib::EndType::Polygon); + Paths64 solution = InflatePaths(subjects, -209715, + JoinType::Miter, EndType::Polygon); EXPECT_LE(solution[0].size() - subjects[0].size(), 1); } TEST(Clipper2Tests, TestOffsets4) // see #482 { - Clipper2Lib::Paths64 paths = { { {0, 0}, {20000, 200}, + Paths64 paths = { { {0, 0}, {20000, 200}, {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} }; - Clipper2Lib::Paths64 solution = Clipper2Lib::InflatePaths(paths, -5000, - Clipper2Lib::JoinType::Square, Clipper2Lib::EndType::Polygon); + Paths64 solution = InflatePaths(paths, -5000, + JoinType::Square, EndType::Polygon); std::cout << solution[0].size() << std::endl; EXPECT_EQ(solution[0].size(), 5); paths = { { {0, 0}, {20000, 400}, {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} }; - solution = Clipper2Lib::InflatePaths(paths, -5000, - Clipper2Lib::JoinType::Square, Clipper2Lib::EndType::Polygon); + solution = InflatePaths(paths, -5000, + JoinType::Square, EndType::Polygon); std::cout << solution[0].size() << std::endl; EXPECT_EQ(solution[0].size(), 5); paths = { { {0, 0}, {20000, 400}, {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} }; - solution = Clipper2Lib::InflatePaths(paths, -5000, - Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); + solution = InflatePaths(paths, -5000, + JoinType::Round, EndType::Polygon); std::cout << solution[0].size() << std::endl; EXPECT_EQ(solution[0].size(), 5); paths = { { {0, 0}, {20000, 1500}, {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} }; - solution = Clipper2Lib::InflatePaths(paths, -5000, - Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); + solution = InflatePaths(paths, -5000, + JoinType::Round, EndType::Polygon); std::cout << solution[0].size() << std::endl; EXPECT_GT(solution[0].size(), 6); diff --git a/CPP/Tests/TestPolytreeHoles2.cpp b/CPP/Tests/TestPolytreeHoles2.cpp index c28c72cb..aa2c5632 100644 --- a/CPP/Tests/TestPolytreeHoles2.cpp +++ b/CPP/Tests/TestPolytreeHoles2.cpp @@ -46,14 +46,14 @@ double GetPolytreeArea(const PolyPath64& pp) TEST(Clipper2Tests, TestPolytreeHoles2) { std::ifstream ifs("PolytreeHoleOwner2.txt"); - + //if (!ifs.good()) return; ASSERT_TRUE(ifs); ASSERT_TRUE(ifs.good()); Paths64 subject, subject_open, clip; - ClipType ct; - FillRule fr; - int64_t area, count; + ClipType ct = ClipType::None; + 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/Utils/Timer.h b/CPP/Utils/Timer.h index ee9792ac..3f1e8a7c 100644 --- a/CPP/Utils/Timer.h +++ b/CPP/Utils/Timer.h @@ -5,31 +5,25 @@ #include #include #include +#include /* Timer Usage: The Timer object will start immediately following its construction -(unless "start_paused" is true). It will stop either when its -destructor is called on leaving scope, or when pause() is called. -The timer's pause() and resume() can be called an number of times and -the total interval while unpaused will be reported in standard output. - -The Timer's constructor takes two optional string parameters: - caption : test sent to standard output just as timing commences. - time_text : text to be output to the left of the recorded time interval. +(unless "start_paused" is true). The timer's pause() and resume() can +be called an number of times and the total interval while unpaused +will be returned when elapsed() is called. Example: #include "timer.h" - #include "windows.h" //for Sleep() :) void main() { - { - Timer timer("Starting timer now.", "This operation took "); - Sleep(1000); - } + Timer timer; + Sleep(1000); + std::cout << timer.elapsed_str(); } */ @@ -38,31 +32,16 @@ struct Timer { private: std::streamsize old_precision = std::cout.precision(0); std::ios_base::fmtflags old_flags = std::cout.flags(); - std::chrono::high_resolution_clock::time_point time_started_ = - std::chrono::high_resolution_clock::now(); + std::chrono::high_resolution_clock::time_point time_started_; std::chrono::high_resolution_clock::duration duration_ = {}; bool paused_ = false; - std::string time_text_ = ""; public: - Timer(bool start_paused = false): paused_(start_paused) {} - - explicit Timer(const char time_text[], bool start_paused) : - paused_(start_paused), time_text_(time_text) {} - - explicit Timer(const char caption[], const char time_text[] = "", - bool start_paused = false) : - paused_(start_paused), time_text_(time_text) - { - std::cout << caption << std::endl; - } - - explicit Timer(const std::string& caption, const std::string& time_text = "", - bool start_paused = false) : - paused_(start_paused), time_text_(time_text) + Timer(bool start_paused = false): paused_(start_paused) { - if (caption != "") std::cout << caption << std::endl; + if (!paused_) time_started_ = + std::chrono::high_resolution_clock::now(); } void resume() @@ -81,24 +60,31 @@ struct Timer { paused_ = true; } - ~Timer() + int64_t elapsed_nano() // result in nano-seconds + { + if (!paused_) + { + std::chrono::high_resolution_clock::time_point now = + std::chrono::high_resolution_clock::now(); + duration_ += (now - time_started_); + } + return std::chrono::duration_cast(duration_).count(); + } + + std::string elapsed_str() // result as string { - pause(); - int nsecs_log10 = static_cast(std::log10( - std::chrono::duration_cast(duration_).count())); - std::cout << std::fixed << - std::setprecision(static_cast(2.0 - (nsecs_log10 % 3))) << time_text_; - if (nsecs_log10 < 6) - std::cout << std::chrono::duration_cast - (duration_).count() * 1.0e-3 << " microsecs" << std::endl; + int64_t nano_secs = elapsed_nano(); + int nsecs_log10 = static_cast(std::log10(nano_secs)); + std::ostringstream os{}; + os.precision(static_cast(2.0 - (nsecs_log10 % 3))); + os << std::fixed; + if (nsecs_log10 < 6) + os << nano_secs * 1.0e-3 << " microsecs"; else if (nsecs_log10 < 9) - std::cout << std::chrono::duration_cast - (duration_).count() * 1.0e-3 << " millisecs" << std::endl; - else - std::cout << std::chrono::duration_cast - (duration_).count() * 1.0e-3 << " secs" << std::endl; - std::cout.precision(old_precision); - std::cout.flags(old_flags); + os << nano_secs * 1.0e-6 << " millisecs"; + else + os << nano_secs * 1.0e-9 << " secs"; + return os.str(); } }; diff --git a/CPP/Utils/clipper.svg.cpp b/CPP/Utils/clipper.svg.cpp index d8b11653..c7b1894e 100644 --- a/CPP/Utils/clipper.svg.cpp +++ b/CPP/Utils/clipper.svg.cpp @@ -248,11 +248,12 @@ namespace Clipper2Lib { } } - //draw red dots at all vertices - useful for debugging + ////draw red dots at all solution vertices - useful for debugging //for (PathInfo* pi : path_infos) - // for (PathD& path : pi->paths_) - // for (PointD& pt : path) - // DrawCircle(file, pt.x * scale + offsetX, pt.y * scale + offsetY, 1); + // if (!(pi->pen_color_ & 0x00FF00FF)) // ie any shade of green only + // for (PathD& path : pi->paths_) + // for (PointD& pt : path) + // DrawCircle(file, pt.x * scale + offsetX, pt.y * scale + offsetY, 1.6); for (TextInfo* ti : text_infos) { @@ -410,8 +411,9 @@ namespace Clipper2Lib { bool SvgReader::LoadFromFile(const std::string &filename) { Clear(); - std::ifstream file; - file.open(filename); + std::ifstream file(filename); + if (!file.good()) return false; + std::stringstream xml_buff; xml_buff << file.rdbuf(); file.close(); diff --git a/CSharp/Clipper2Lib/Clipper.Core.cs b/CSharp/Clipper2Lib/Clipper.Core.cs index 8ba9e85b..4f526677 100644 --- a/CSharp/Clipper2Lib/Clipper.Core.cs +++ b/CSharp/Clipper2Lib/Clipper.Core.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 1 May 2023 * +* Date : 16 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core structures and functions for the Clipper Library * @@ -647,25 +647,25 @@ internal static bool GetIntersectPt(Point64 ln1a, [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool GetIntersectPoint(Point64 ln1a, - Point64 ln1b, Point64 ln2a, Point64 ln2b, out PointD ip) + Point64 ln1b, Point64 ln2a, Point64 ln2b, out Point64 ip) { double dy1 = (ln1b.Y - ln1a.Y); double dx1 = (ln1b.X - ln1a.X); double dy2 = (ln2b.Y - ln2a.Y); double dx2 = (ln2b.X - ln2a.X); - double q1 = dy1 * ln1a.X - dx1 * ln1a.Y; - double q2 = dy2 * ln2a.X - dx2 * ln2a.Y; - double cross_prod = dy1 * dx2 - dy2 * dx1; - if (cross_prod == 0.0) + double det = dy1 * dx2 - dy2 * dx1; + if (det == 0.0) { - ip = new PointD(); + ip = new Point64(); return false; } - ip = new PointD( - (dx2 * q1 - dx1 * q2) / cross_prod, - (dy2 * q1 - dy1 * q2) / cross_prod); + double t = ((ln1a.X - ln2a.X) * dy2 - (ln1a.Y - ln2a.Y) * dx2) / det; + if (t <= 0.0) ip = ln1a; // ?? check further (see also #568) + else if (t >= 1.0) ip = ln2a; // ?? check further + else ip = new Point64 (ln1a.X + t * dx1, ln1a.Y + t * dy1); return true; } + internal static bool SegsIntersect(Point64 seg1a, Point64 seg1b, Point64 seg2a, Point64 seg2b, bool inclusive = false) { diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 5fa174da..53084cfb 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 17 June 2023 * +* Date : 16 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2705,7 +2705,7 @@ private void ProcessHorzJoins() op1b.prev = op2b; op2b.next = op1b; - if (or1 == or2) + if (or1 == or2) // 'join' is really a split { or2 = NewOutRec(); or2.pts = op1b; @@ -2717,21 +2717,18 @@ private void ProcessHorzJoins() or1.pts.outrec = or1; } - if (_using_polytree) + if (_using_polytree) //#498, #520, #584, D#576 { - if (Path1InsidePath2(or2.pts, or1.pts)) + if (Path1InsidePath2(or1.pts, or2.pts)) { - SetOwner(or2, or1); - or1.splits ??= new List(); - or1.splits.Add(or2.idx); // (#520) - } - else if (Path1InsidePath2(or1.pts, or2.pts)) + or2.owner = or1.owner; SetOwner(or1, or2); + } else { + SetOwner(or2, or1); or1.splits ??= new List(); - or1.splits.Add(or2.idx); // (#498) - or2.owner = or1; + or1.splits.Add(or2.idx); } } else @@ -2832,8 +2829,7 @@ private void DoSplitOp(OutRec outrec, OutPt splitOp) OutPt result = prevOp; InternalClipper.GetIntersectPoint( - prevOp.pt, splitOp.pt, splitOp.next.pt, nextNextOp.pt, out PointD tmp); - Point64 ip = new Point64(tmp); + prevOp.pt, splitOp.pt, splitOp.next.pt, nextNextOp.pt, out Point64 ip); #if USINGZ if (_zCallback != null) @@ -2849,11 +2845,6 @@ private void DoSplitOp(OutRec outrec, OutPt splitOp) return; } - // nb: area1 is the path's area *before* splitting, whereas area2 is - // the area of the triangle containing splitOp & splitOp.next. - // So the only way for these areas to have the same sign is if - // the split triangle is larger than the path containing prevOp or - // if there's more than one self=intersection. double area2 = AreaTriangle(ip, splitOp.pt, splitOp.next.pt); double absArea2 = Math.Abs(area2); @@ -2866,13 +2857,16 @@ private void DoSplitOp(OutRec outrec, OutPt splitOp) } else { - OutPt newOp2 = new - OutPt(ip, outrec) { prev = prevOp, next = nextNextOp }; - + OutPt newOp2 = new OutPt(ip, outrec) { prev = prevOp, next = nextNextOp }; nextNextOp.prev = newOp2; prevOp.next = newOp2; } + // nb: area1 is the path's area *before* splitting, whereas area2 is + // the area of the triangle containing splitOp & splitOp.next. + // So the only way for these areas to have the same sign is if + // the split triangle is larger than the path containing prevOp or + // if there's more than one self=intersection. if (absArea2 > 1 && (absArea2 > absArea1 || ((area2 > 0) == (area1 > 0)))) @@ -2882,16 +2876,24 @@ private void DoSplitOp(OutRec outrec, OutPt splitOp) splitOp.outrec = newOutRec; splitOp.next.outrec = newOutRec; - if (_using_polytree) - { - outrec.splits ??= new List(); - outrec.splits.Add(newOutRec.idx); - } - 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); + } + } } //else { splitOp = null; splitOp.next = null; } } diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index 635f7a6d..7c175521 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 May 2023 * +* Date : 16 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -460,7 +460,10 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) else if (sinA < -1.0) sinA = -1.0; if (DeltaCallback != null) + { _groupDelta = DeltaCallback(path, _normals, j, k); + if (group.pathsReversed) _groupDelta = -_groupDelta; + } if (Math.Abs(_groupDelta) < Tolerance) { group.outPath.Add(path[j]); diff --git a/CSharp/Clipper2Lib/Clipper.cs b/CSharp/Clipper2Lib/Clipper.cs index b32778a6..dc7ccf64 100644 --- a/CSharp/Clipper2Lib/Clipper.cs +++ b/CSharp/Clipper2Lib/Clipper.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 30 May 2023 * +* Date : 16 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module contains simple functions that will likely cover * diff --git a/CSharp/Clipper2Lib/Clipper2Lib.csproj b/CSharp/Clipper2Lib/Clipper2Lib.csproj index 9762988d..505c1e8a 100644 --- a/CSharp/Clipper2Lib/Clipper2Lib.csproj +++ b/CSharp/Clipper2Lib/Clipper2Lib.csproj @@ -1,10 +1,9 @@ - netstandard2.0 + netstandard2.1 enable 8 - 1.1.0 True Angus Johnson Polygon Clipping and Offsetting Library @@ -30,4 +29,8 @@ + + + + diff --git a/CSharp/USINGZ/Clipper2LibZ.csproj b/CSharp/USINGZ/Clipper2LibZ.csproj index b36c9182..f9b0fb7e 100644 --- a/CSharp/USINGZ/Clipper2LibZ.csproj +++ b/CSharp/USINGZ/Clipper2LibZ.csproj @@ -1,7 +1,7 @@ - net6.0 + netstandard2.1 enable 8 @@ -14,8 +14,4 @@ TRACE;DEBUG;USINGZ; - - - - diff --git a/CSharp/Utils/ClipFileIO/Clipper.FileIO.csproj b/CSharp/Utils/ClipFileIO/Clipper.FileIO.csproj index 9fb89b54..dcc7d1ee 100644 --- a/CSharp/Utils/ClipFileIO/Clipper.FileIO.csproj +++ b/CSharp/Utils/ClipFileIO/Clipper.FileIO.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.1 enable 8 AnyCPU;x86 diff --git a/CSharp/Utils/Colors/Clipper.Colors.csproj b/CSharp/Utils/Colors/Clipper.Colors.csproj index 35c50604..025e5fba 100644 --- a/CSharp/Utils/Colors/Clipper.Colors.csproj +++ b/CSharp/Utils/Colors/Clipper.Colors.csproj @@ -1,9 +1,9 @@ - netstandard2.0 + netstandard2.1 enable - 10 + 8 AnyCPU;x86 diff --git a/CSharp/Utils/SVG/Clipper2.SVG.csproj b/CSharp/Utils/SVG/Clipper2.SVG.csproj index 27ea7f6c..025e5fba 100644 --- a/CSharp/Utils/SVG/Clipper2.SVG.csproj +++ b/CSharp/Utils/SVG/Clipper2.SVG.csproj @@ -1,7 +1,9 @@ - netstandard2.0 + netstandard2.1 + enable + 8 AnyCPU;x86 diff --git a/Delphi/Clipper2Lib/Clipper.Core.pas b/Delphi/Clipper2Lib/Clipper.Core.pas index 09a696c0..4bf5f9b3 100644 --- a/Delphi/Clipper2Lib/Clipper.Core.pas +++ b/Delphi/Clipper2Lib/Clipper.Core.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 19 February 2023 * +* Date : 16 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library module * @@ -120,6 +120,7 @@ TListEx = class protected function UnsafeGet(idx: integer): Pointer; // no range checking procedure UnsafeSet(idx: integer; val: Pointer); + procedure UnsafeDelete(index: integer); virtual; public constructor Create(capacity: integer = 0); virtual; destructor Destroy; override; @@ -347,6 +348,9 @@ procedure CheckPrecisionRange(var precision: integer); NullRectD : TRectD = (left: 0; top: 0; right: 0; Bottom: 0); Tolerance : Double = 1.0E-12; + //https://github.com/AngusJohnson/Clipper2/discussions/564 + MaxDecimalPrecision = 8; + implementation resourcestring @@ -608,6 +612,14 @@ procedure TListEx.UnsafeSet(idx: integer; val: Pointer); end; //------------------------------------------------------------------------------ +procedure TListEx.UnsafeDelete(index: integer); +begin + dec(fCount); + if index < fCount then + Move(fList[index +1], fList[index], (fCount - index) * SizeOf(Pointer)); +end; +//------------------------------------------------------------------------------ + procedure TListEx.Swap(idx1, idx2: integer); var p: Pointer; @@ -623,7 +635,7 @@ procedure TListEx.Swap(idx1, idx2: integer); procedure CheckPrecisionRange(var precision: integer); begin - if (precision < -8) or (precision > 8) then + if (precision < -MaxDecimalPrecision) or (precision > MaxDecimalPrecision) then Raise EClipper2LibException(rsClipper_PrecisonErr); end; //------------------------------------------------------------------------------ @@ -1922,19 +1934,10 @@ function __Trunc(val: double): Int64; {$IFDEF INLINE} inline; {$ENDIF} end; //------------------------------------------------------------------------------ -function CheckCastInt64(val: double): Int64; {$IFDEF INLINE} inline; {$ENDIF} -begin - if (val >= MaxCoord) or (val <= MinCoord) then - Raise EClipper2LibException.Create('overflow error.'); - Result := Trunc(val); - //Result := __Trunc(val); -end; -//------------------------------------------------------------------------------ - function GetIntersectPoint(const ln1a, ln1b, ln2a, ln2b: TPoint64; out ip: TPoint64): Boolean; var - dx1,dy1, dx2,dy2, qx,qy, cp: double; + dx1,dy1, dx2,dy2, t, cp: double; begin // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection dy1 := (ln1b.y - ln1a.y); @@ -1942,16 +1945,13 @@ function GetIntersectPoint(const ln1a, ln1b, ln2a, ln2b: TPoint64; dy2 := (ln2b.y - ln2a.y); dx2 := (ln2b.x - ln2a.x); cp := dy1 * dx2 - dy2 * dx1; - if (cp = 0.0) then - begin - Result := false; - Exit; - end; - qx := dx1 * ln1a.y - dy1 * ln1a.x; - qy := dx2 * ln2a.y - dy2 * ln2a.x; - ip.X := CheckCastInt64((dx1 * qy - dx2 * qx) / cp); - ip.Y := CheckCastInt64((dy1 * qy - dy2 * qx) / cp); - Result := (ip.x <> invalid64) and (ip.y <> invalid64); + Result := (cp <> 0.0); + if not Result then Exit; + t := ((ln1a.x-ln2a.x) * dy2 - (ln1a.y-ln2a.y) * dx2) / cp; + if t <= 0.0 then ip := ln1a + else if t >= 1.0 then ip := ln2a; + ip.X := Trunc(ln1a.X + t * dx1); + ip.Y := Trunc(ln1a.Y + t * dy1); end; //------------------------------------------------------------------------------ diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index 166875bb..24ea8b51 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 17 June 2023 * +* Date : 16 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -251,7 +251,7 @@ TClipperBase = class procedure ProcessIntersectList; procedure SwapPositionsInAEL(e1, e2: PActive); function AddOutPt(e: PActive; const pt: TPoint64): POutPt; - procedure Split(e: PActive; const currPt: TPoint64); + procedure UndoJoin(e: PActive; const currPt: TPoint64); procedure CheckJoinLeft(e: PActive; const pt: TPoint64; checkCurrX: Boolean = false); {$IFDEF INLINING} inline; {$ENDIF} @@ -550,13 +550,6 @@ function IsOpen(e: PActive): Boolean; overload; {$IFDEF INLINING} inline; {$ENDI end; //------------------------------------------------------------------------------ -//function IsOpenEnd(e: PActive): Boolean; overload; {$IFDEF INLINING} inline; {$ENDIF} -//begin -// Result := e.locMin.isOpen and -// (e.vertTop.flags * [vfOpenStart, vfOpenEnd] <> []); -//end; -//------------------------------------------------------------------------------ - function IsOpenEnd(v: PVertex): Boolean; overload; {$IFDEF INLINING} inline; {$ENDIF} begin Result := (v.flags * [vfOpenStart, vfOpenEnd] <> []); @@ -823,6 +816,120 @@ function GetMaximaPair(e: PActive): PActive; end; //------------------------------------------------------------------------------ +function GetBounds(op: POutPt): TRect64; +var + op2: POutPt; +begin + result.Left := op.pt.X; + result.Right := op.pt.X; + result.Top := op.pt.Y; + result.Bottom := op.pt.Y; + op2 := op.next; + while op2 <> op do + begin + if op2.pt.X < result.Left then result.Left := op2.pt.X + else if op2.pt.X > result.Right then result.Right := op2.pt.X; + if op2.pt.Y < result.Top then result.Top := op2.pt.Y + else if op2.pt.Y > result.Bottom then result.Bottom := op2.pt.Y; + op2 := op2.next; + end; +end; +//------------------------------------------------------------------------------ + +function PointInOpPolygon(const pt: TPoint64; op: POutPt): TPointInPolygonResult; +var + val: Integer; + op2: POutPt; + isAbove, startingAbove: Boolean; + d: double; // avoids integer overflow +begin + result := pipOutside; + if (op = op.next) or (op.prev = op.next) then Exit; + + op2 := op; + repeat + if (op.pt.Y <> pt.Y) then break; + op := op.next; + until op = op2; + if (op.pt.Y = pt.Y) then Exit; // not a proper polygon + + isAbove := op.pt.Y < pt.Y; + startingAbove := isAbove; + Result := pipOn; + val := 0; + op2 := op.next; + while (op2 <> op) do + begin + if isAbove then + while (op2 <> op) and (op2.pt.Y < pt.Y) do op2 := op2.next + else + 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 + // and this must happen an even number of times + + if (op2.pt.Y = pt.Y) then // touching the horizontal + begin + if (op2.pt.X = pt.X) or ((op2.pt.Y = op2.prev.pt.Y) and + ((pt.X < op2.prev.pt.X) <> (pt.X < op2.pt.X))) then Exit; + op2 := op2.next; + if (op2 = op) then break; + Continue; + end; + + if (pt.X < op2.pt.X) and (pt.X < op2.prev.pt.X) then + // do nothing because + // we're only interested in edges crossing on the left + else if((pt.X > op2.prev.pt.X) and (pt.X > op2.pt.X)) then + val := 1 - val // toggle val + else + begin + d := CrossProduct(op2.prev.pt, op2.pt, pt); + if d = 0 then Exit; // ie point on path + if (d < 0) = isAbove then val := 1 - val; + end; + isAbove := not isAbove; + op2 := op2.next; + end; + + if (isAbove <> startingAbove) then + begin + d := CrossProduct(op2.prev.pt, op2.pt, pt); + if d = 0 then Exit; // ie point on path + if (d < 0) = isAbove then val := 1 - val; + end; + + if val = 0 then + result := pipOutside else + result := pipInside; +end; +//------------------------------------------------------------------------------ + +function Path1InsidePath2(const op1, op2: POutPt): Boolean; +var + op: POutPt; + pipResult: TPointInPolygonResult; + outsideCnt: integer; +begin + // precondition - the twi paths or1 & pr2 don't intersect + // we need to make some accommodation for rounding errors + // so we won't jump if the first vertex is found outside + outsideCnt := 0; + op := op1; + repeat + pipResult := PointInOpPolygon(op.pt, op2); + if pipResult = pipOutside then inc(outsideCnt) + else if pipResult = pipInside then dec(outsideCnt); + op := op.next; + until (op = op1) or (Abs(outsideCnt) = 2); + // if path1's location is still equivocal then check its midpoint + if Abs(outsideCnt) > 1 then + Result := outsideCnt < 0 else + Result := PointInOpPolygon(GetBounds(op).MidPoint, op2) = pipInside; +end; +//------------------------------------------------------------------------------ + function PointCount(pts: POutPt): Integer; {$IFDEF INLINING} inline; {$ENDIF} var p: POutPt; @@ -2035,11 +2142,6 @@ procedure TClipperBase.DoSplitOp(outrec: POutRec; splitOp: POutPt); Exit; end; - // nb: area1 is the path's area *before* splitting, whereas area2 is - // the area of the triangle containing splitOp & splitOp.next. - // So the only way for these areas to have the same sign is if - // the split triangle is larger than the path containing prevOp or - // if there's more than one self-intersection. area2 := AreaTriangle(ip, splitOp.pt, splitOp.next.pt); absArea2 := abs(area2); @@ -2057,6 +2159,11 @@ procedure TClipperBase.DoSplitOp(outrec: POutRec; splitOp: POutPt); prevOp.next := newOp2; end; + // nb: area1 is the path's area *before* splitting, whereas area2 is + // the area of the triangle containing splitOp & splitOp.next. + // So the only way for these areas to have the same sign is if + // the split triangle is larger than the path containing prevOp or + // if there's more than one self-intersection. if (absArea2 > 1) and ((absArea2 > absArea1) or ((area2 > 0) = (area1 > 0))) then @@ -2064,15 +2171,20 @@ procedure TClipperBase.DoSplitOp(outrec: POutRec; splitOp: POutPt); newOutRec := FOutRecList.Add; newOutRec.owner := outrec.owner; - if FUsingPolytree then - AddSplit(outrec, newOutRec); - splitOp.outrec := newOutRec; splitOp.next.outrec := newOutRec; newOp := NewOutPt(ip, newOutRec, splitOp.next, splitOp); splitOp.prev := newOp; splitOp.next.next := newOp; newOutRec.pts := newOp; + + if FUsingPolytree then + begin + if (Path1InsidePath2(prevOp, newOp)) then + AddSplit(newOutRec, outrec) else + AddSplit(outrec, newOutRec); + end; + end else begin Dispose(splitOp.next); @@ -2111,8 +2223,8 @@ function TClipperBase.AddLocalMaxPoly(e1, e2: PActive; const pt: TPoint64): POut outRec: POutRec; begin - if IsJoined(e1) then Split(e1, pt); - if IsJoined(e2) then Split(e2, pt); + if IsJoined(e1) then UndoJoin(e1, pt); + if IsJoined(e2) then UndoJoin(e2, pt); if (IsFront(e1) = IsFront(e2)) then begin @@ -2197,12 +2309,25 @@ procedure TClipperBase.JoinOutrecPaths(e1, e2: PActive); e2.outrec.frontE := nil; e2.outrec.backE := nil; e2.outrec.pts := nil; - SetOwner(e2.outrec, e1.outrec); if IsOpenEnd(e1.vertTop) then begin e2.outrec.pts := e1.outrec.pts; e1.outrec.pts := nil; + end else + begin + SetOwner(e2.outrec, e1.outrec); + +// if FUsingPolytree then +// begin +// e := GetPrevHotEdge(e1); +// if not Assigned(e) then +// outRec.owner := nil else +// SetOwner(outRec, e.outrec); +// // nb: outRec.owner here is likely NOT the real +// // owner but this will be checked in DeepCheckOwner() +// end; + end; // and e1 and e2 are maxima and are about to be dropped from the Actives list. @@ -2211,7 +2336,7 @@ procedure TClipperBase.JoinOutrecPaths(e1, e2: PActive); end; //------------------------------------------------------------------------------ -procedure TClipperBase.Split(e: PActive; const currPt: TPoint64); +procedure TClipperBase.UndoJoin(e: PActive; const currPt: TPoint64); begin if e.joinedWith = jwRight then begin @@ -2347,7 +2472,7 @@ procedure TClipperBase.UpdateEdgeIntoAEL(var e: PActive); e.currX := e.bot.X; SetDx(e); - if IsJoined(e) then Split(e, e.bot); + if IsJoined(e) then UndoJoin(e, e.bot); if IsHorizontal(e) then Exit; InsertScanLine(e.top.Y); @@ -2399,7 +2524,7 @@ function TClipperBase.IntersectEdges(e1, e2: PActive; pt: TPoint64): POutPt; // e1 is open and e2 is closed - if IsJoined(e2) then Split(e2, pt); // needed for safety + if IsJoined(e2) then UndoJoin(e2, pt); // needed for safety case FClipType of ctUnion: if not IsHotEdge(e2) then Exit; @@ -2450,8 +2575,8 @@ function TClipperBase.IntersectEdges(e1, e2: PActive; pt: TPoint64): POutPt; end; // MANAGING CLOSED PATHS FROM HERE ON - if IsJoined(e1) then Split(e1, pt); - if IsJoined(e2) then Split(e2, pt); + if IsJoined(e1) then UndoJoin(e1, pt); + if IsJoined(e2) then UndoJoin(e2, pt); // FIRST, UPDATE WINDING COUNTS if IsSamePolyType(e1, e2) then @@ -2723,120 +2848,6 @@ procedure SetOutRecPts(op: POutPt; newOR: POutrec); overload; end; //------------------------------------------------------------------------------ -function GetBounds(op: POutPt): TRect64; -var - op2: POutPt; -begin - result.Left := op.pt.X; - result.Right := op.pt.X; - result.Top := op.pt.Y; - result.Bottom := op.pt.Y; - op2 := op.next; - while op2 <> op do - begin - if op2.pt.X < result.Left then result.Left := op2.pt.X - else if op2.pt.X > result.Right then result.Right := op2.pt.X; - if op2.pt.Y < result.Top then result.Top := op2.pt.Y - else if op2.pt.Y > result.Bottom then result.Bottom := op2.pt.Y; - op2 := op2.next; - end; -end; -//------------------------------------------------------------------------------ - -function PointInOpPolygon(const pt: TPoint64; op: POutPt): TPointInPolygonResult; -var - val: Integer; - op2: POutPt; - isAbove, startingAbove: Boolean; - d: double; // avoids integer overflow -begin - result := pipOutside; - if (op = op.next) or (op.prev = op.next) then Exit; - - op2 := op; - repeat - if (op.pt.Y <> pt.Y) then break; - op := op.next; - until op = op2; - if (op.pt.Y = pt.Y) then Exit; // not a proper polygon - - isAbove := op.pt.Y < pt.Y; - startingAbove := isAbove; - Result := pipOn; - val := 0; - op2 := op.next; - while (op2 <> op) do - begin - if isAbove then - while (op2 <> op) and (op2.pt.Y < pt.Y) do op2 := op2.next - else - 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 - // and this must happen an even number of times - - if (op2.pt.Y = pt.Y) then // touching the horizontal - begin - if (op2.pt.X = pt.X) or ((op2.pt.Y = op2.prev.pt.Y) and - ((pt.X < op2.prev.pt.X) <> (pt.X < op2.pt.X))) then Exit; - op2 := op2.next; - if (op2 = op) then break; - Continue; - end; - - if (pt.X < op2.pt.X) and (pt.X < op2.prev.pt.X) then - // do nothing because - // we're only interested in edges crossing on the left - else if((pt.X > op2.prev.pt.X) and (pt.X > op2.pt.X)) then - val := 1 - val // toggle val - else - begin - d := CrossProduct(op2.prev.pt, op2.pt, pt); - if d = 0 then Exit; // ie point on path - if (d < 0) = isAbove then val := 1 - val; - end; - isAbove := not isAbove; - op2 := op2.next; - end; - - if (isAbove <> startingAbove) then - begin - d := CrossProduct(op2.prev.pt, op2.pt, pt); - if d = 0 then Exit; // ie point on path - if (d < 0) = isAbove then val := 1 - val; - end; - - if val = 0 then - result := pipOutside else - result := pipInside; -end; -//------------------------------------------------------------------------------ - -function Path1InsidePath2(const op1, op2: POutPt): Boolean; -var - op: POutPt; - pipResult: TPointInPolygonResult; - outsideCnt: integer; -begin - // precondition - the twi paths or1 & pr2 don't intersect - // we need to make some accommodation for rounding errors - // so we won't jump if the first vertex is found outside - outsideCnt := 0; - op := op1; - repeat - pipResult := PointInOpPolygon(op.pt, op2); - if pipResult = pipOutside then inc(outsideCnt) - else if pipResult = pipInside then dec(outsideCnt); - op := op.next; - until (op = op1) or (Abs(outsideCnt) = 2); - // if path1's location is still equivocal then check its midpoint - if Abs(outsideCnt) > 1 then - Result := outsideCnt < 0 else - Result := PointInOpPolygon(GetBounds(op).MidPoint, op2) = pipInside; -end; -//------------------------------------------------------------------------------ - function HorzOverlapWithLRSet(const left1, right1, left2, right2: TPoint64): boolean; {$IFDEF INLINING} inline; {$ENDIF} begin @@ -3006,32 +3017,31 @@ procedure TClipperBase.ProcessHorzJoins; op1b.prev := op2b; op2b.next := op1b; - if or1 = or2 then + if or1 = or2 then // 'join' is really a split begin or2 := FOutRecList.Add; or2.pts := op1b; FixOutRecPts(or2); + if or1.pts.outrec = or2 then begin or1.pts := op1; or1.pts.outrec := or1; end; - if FUsingPolytree then + if FUsingPolytree then //#498, #520, #584, D#576 begin - if Path1InsidePath2(or2.pts, or1.pts) then + if Path1InsidePath2(or1.pts, or2.pts) then begin - SetOwner(or2, or1); - AddSplit(or1, or2); //(#520) - end - else if Path1InsidePath2(or1.pts, or2.pts) then - SetOwner(or1, or2) - else + or2.owner := or1.owner; + SetOwner(or1, or2); + end else begin - AddSplit(or1, or2); //(#498) - or2.owner := or1; + SetOwner(or2, or1); + AddSplit(or1, or2); end; - end else + end + else or2.owner := or1; end else begin @@ -3424,7 +3434,7 @@ procedure TClipperBase.DoHorizontal(horzEdge: PActive); if (e.vertTop = maxVertex) then begin if IsHotEdge(horzEdge) and IsJoined(e) then - Split(e, e.top); + UndoJoin(e, e.top); if IsHotEdge(horzEdge) then begin @@ -3600,8 +3610,8 @@ function TClipperBase.DoMaxima(e: PActive): PActive; if not assigned(eMaxPair) then Exit; // EMaxPair is a horizontal ... end; - if IsJoined(e) then Split(e, e.top); - if IsJoined(eMaxPair) then Split(eMaxPair, eMaxPair.top); + if IsJoined(e) then UndoJoin(e, e.top); + if IsJoined(eMaxPair) then UndoJoin(eMaxPair, eMaxPair.top); // only non-horizontal maxima here. // process any edges between maxima pair ... diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index f111628f..ed406869 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 15 May 2023 * +* Date : 16 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -460,7 +460,7 @@ procedure TClipperOffset.OffsetOpenPath; begin highI := high(fInPath); - if Assigned(fDeltaCallback64) then + if Assigned(fDeltaCallback64) then fGroupDelta := fDeltaCallback64(fInPath, fNorms, 0, 0); // do the line start cap @@ -502,7 +502,7 @@ procedure TClipperOffset.OffsetOpenPath; // do the line end cap - if Assigned(fDeltaCallback64) then + if Assigned(fDeltaCallback64) then fGroupDelta := fDeltaCallback64(fInPath, fNorms, highI, highI); if Abs(fGroupDelta) < Tolerance then begin @@ -807,8 +807,8 @@ procedure TClipperOffset.DoRound(j, k: Integer; angle: double); if Assigned(fDeltaCallback64) then begin - // when fDeltaCallback64 is assigned, fGroupDelta won't be constant, - // so we'll need to do the following calculations for *every* vertex. + // when fDeltaCallback64 is assigned, fGroupDelta won't be constant, + // so we'll need to do the following calculations for *every* vertex. absDelta := Abs(fGroupDelta); if fArcTolerance > 0.01 then arcTol := Min(absDelta, fArcTolerance) else @@ -867,14 +867,17 @@ procedure TClipperOffset.OffsetPoint(j: Integer; var k: integer); if (sinA > 1.0) then sinA := 1.0 else if (sinA < -1.0) then sinA := -1.0; - if Assigned(fDeltaCallback64) then + if Assigned(fDeltaCallback64) then + begin fGroupDelta := fDeltaCallback64(fInPath, fNorms, j, k); + if TGroup(fGroupList[0]).reversed then fGroupDelta := -fGroupDelta; + end; - if Abs(fGroupDelta) <= Tolerance then - begin - AddPoint(fInPath[j]); - Exit; - end; + if Abs(fGroupDelta) <= Tolerance then + begin + AddPoint(fInPath[j]); + Exit; + end; if (cosA > 0.999) then // almost straight - less than 2.5 degree (#424, #526) DoMiter(j, k, cosA) diff --git a/Delphi/Clipper2Lib/Clipper.pas b/Delphi/Clipper2Lib/Clipper.pas index d54bbaec..4fc8d203 100644 --- a/Delphi/Clipper2Lib/Clipper.pas +++ b/Delphi/Clipper2Lib/Clipper.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 26 May 2023 * +* Date : 16 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -123,6 +123,15 @@ function MinkowskiSum(const pattern, path: TPathD; function PolyTreeToPaths64(PolyTree: TPolyTree64): TPaths64; function PolyTreeToPathsD(PolyTree: TPolyTreeD): TPathsD; +function PathToString(const p: TPath64; + indentSpaces: integer = 0; pointsPerRow: integer = 0): string; overload; +function PathToString(const p: TPathD; decimals: integer; + indentSpaces: integer = 0; pointsPerRow: integer = 0): string; overload; +function PathsToString(const p: TPaths64; + indentSpaces: integer = 0; pointsPerRow: integer = 0): string; overload; +function PathsToString(const p: TPathsD; decimals: integer; + indentSpaces: integer = 0; pointsPerRow: integer = 0): string; overload; + //ShowPolyTreeStructure: only useful when debugging procedure ShowPolyTreeStructure(polytree: TPolyTree64; strings: TStrings); overload; procedure ShowPolyTreeStructure(polytree: TPolyTreeD; strings: TStrings); overload; @@ -136,14 +145,12 @@ function TrimCollinear(const path: TPathD; precision: integer; isOpenPath: Boolean = false): TPathD; overload; function PointInPolygon(const pt: TPoint64; const polygon: TPath64): - TPointInPolygonResult; {$IFDEF INLINE} inline; {$ENDIF} + TPointInPolygonResult; function SimplifyPath(const path: TPath64; - epsilon: double; isClosedPath: Boolean = false): TPath64; - {$IFDEF INLINE} inline; {$ENDIF} + shapeTolerance: double; isOpenPath: Boolean): TPath64; function SimplifyPaths(const paths: TPaths64; - epsilon: double; isClosedPaths: Boolean = false): TPaths64; - {$IFDEF INLINE} inline; {$ENDIF} + shapeTolerance: double; isOpenPaths: Boolean): TPaths64; implementation @@ -612,22 +619,81 @@ function MinkowskiSum(const pattern, path: TPathD; end; //------------------------------------------------------------------------------ -procedure ShowPolyPathStructure64(pp: TPolyPath64; level: integer; strings: TStrings); +function PathToString(const p: TPath64; + indentSpaces: integer; pointsPerRow: integer): string; +var + i, highI: Integer; + spaces: string; +begin + spaces := StringOfChar(' ', indentSpaces); + Result := spaces; + highI := high(p); + if highI < 0 then Exit; + for i := 0 to highI -1 do + begin + Result := Result + format('%d,%d, ',[p[i].X,p[i].Y]); + if (pointsPerRow > 0) and ((i + 1) mod pointsPerRow = 0) then + Result := Result + #10 + spaces; + end; + Result := Result + format('%d,%d',[p[highI].X,p[highI].Y]); +end; +//------------------------------------------------------------------------------ + +function PathToString(const p: TPathD; decimals: integer; + indentSpaces: integer; pointsPerRow: integer): string; +var + i, highI: Integer; + spaces: string; +begin + spaces := StringOfChar(' ', indentSpaces); + Result := ''; + highI := high(p); + if highI < 0 then Exit; + for i := 0 to highI -1 do + Result := Result + format('%1.*n,%1.*n, ', + [decimals, p[i].X, decimals, p[i].Y]); + Result := Result + format('%1.*n,%1.*n',[ + decimals, p[highI].X, decimals, p[highI].Y]); +end; +//------------------------------------------------------------------------------ + +function PathsToString(const p: TPaths64; + indentSpaces: integer = 0; pointsPerRow: integer = 0): string; +var + i: integer; +begin + Result := ''; + for i := 0 to High(p) do + Result := Result + PathToString(p[i], indentSpaces, pointsPerRow) + #10#10; +end; +//------------------------------------------------------------------------------ + +function PathsToString(const p: TPathsD; decimals: integer; + indentSpaces: integer = 0; pointsPerRow: integer = 0): string; var i: integer; - spaces, caption: string; +begin + Result := ''; + for i := 0 to High(p) do + Result := Result + PathToString(p[i], indentSpaces, pointsPerRow) + #10#10; +end; +//------------------------------------------------------------------------------ + +procedure ShowPolyPathStructure64(pp: TPolyPath64; level: integer; + strings: TStrings); +var + i: integer; + spaces, plural: string; begin spaces := StringOfChar(' ', level * 2); + if pp.Count = 1 then plural := '' else plural := 's'; if pp.IsHole then - caption := 'Hole' else - caption := 'Outer'; - if (pp.Count > 0) then - begin - strings.Add(Format('%s%s (%d)',[spaces, caption, pp.Count])); - for i := 0 to pp.Count -1 do + strings.Add(Format('%sA hole containing %d polygon%s', [spaces, pp.Count, plural])) + else + strings.Add(Format('%sA polygon containing %d hole%s', [spaces, pp.Count, plural])); + for i := 0 to pp.Count -1 do + if pp.child[i].Count> 0 then ShowPolyPathStructure64(pp.child[i], level + 1, strings); - end else - strings.Add(spaces + caption); end; //------------------------------------------------------------------------------ @@ -635,28 +701,29 @@ procedure ShowPolyTreeStructure(polytree: TPolyTree64; strings: TStrings); var i: integer; begin - strings.Add('Polytree Root'); + if polytree.Count = 1 then + strings.Add('Polytree with just 1 polygon.') else + strings.Add(Format('Polytree with just %d polygons.', [polytree.Count])); for i := 0 to polytree.Count -1 do - ShowPolyPathStructure64(polytree[i], 1, strings); + if polytree[i].Count > 0 then + ShowPolyPathStructure64(polytree[i], 1, strings); end; //------------------------------------------------------------------------------ procedure ShowPolyPathStructureD(pp: TPolyPathD; level: integer; strings: TStrings); var i: integer; - spaces, caption: string; + spaces, plural: string; begin spaces := StringOfChar(' ', level * 2); + if pp.Count = 1 then plural := '' else plural := 's'; if pp.IsHole then - caption := 'Hole ' else - caption := 'Outer '; - if (pp.Count > 0) then - begin - strings.Add(Format('%s%s (%d)',[spaces + caption, pp.Count])); - for i := 0 to pp.Count -1 do + strings.Add(Format('%sA hole containing %d polygon%s', [spaces, pp.Count, plural])) + else + strings.Add(Format('%sA polygon containing %d hole%s', [spaces, pp.Count, plural])); + for i := 0 to pp.Count -1 do + if pp.child[i].Count> 0 then ShowPolyPathStructureD(pp.child[i], level + 1, strings); - end else - strings.Add(spaces + caption); end; //------------------------------------------------------------------------------ @@ -664,13 +731,15 @@ procedure ShowPolyTreeStructure(polytree: TPolyTreeD; strings: TStrings); var i: integer; begin - strings.Add('Polytree Root'); + if polytree.Count = 1 then + strings.Add('Polytree with just 1 polygon.') else + strings.Add(Format('Polytree with just %d polygons.', [polytree.Count])); for i := 0 to polytree.Count -1 do - ShowPolyPathStructureD(polytree[i], 1, strings); + if polytree[i].Count > 0 then + ShowPolyPathStructureD(polytree[i], 1, strings); end; //------------------------------------------------------------------------------ - function TrimCollinear(const p: TPath64; isOpenPath: Boolean = false): TPath64; var i,j, len: integer; @@ -743,7 +812,14 @@ function PointInPolygon(const pt: TPoint64; end; //------------------------------------------------------------------------------ -function PerpendicDistFromLineSqrd(const pt, line1, line2: TPoint64): double; +function DistanceSqrd(const pt1, pt2: TPoint64): double; + {$IFDEF INLINE} inline; {$ENDIF} +begin + result := Sqr(double(pt1.X) - pt2.X) + Sqr(double(pt1.Y) - pt2.Y); +end; +//------------------------------------------------------------------------------ + +function PerpendicDistSqrd(const pt, line1, line2: TPoint64): double; {$IFDEF INLINE} inline; {$ENDIF} var a,b,c,d: double; @@ -756,121 +832,123 @@ function PerpendicDistFromLineSqrd(const pt, line1, line2: TPoint64): double; result := 0 else result := Sqr(a * d - c * b) / (c * c + d * d); end; -//------------------------------------------------------------------------------ -function GetNext(current, high: integer; var flags: array of Boolean): integer; - {$IFDEF INLINE} inline; {$ENDIF} -begin - Result := current +1; - while (Result <= high) and flags[Result] do inc(Result); - if (Result <= high) then Exit; - Result := 0; - while (flags[Result]) do inc(Result); -end; +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ -function GetPrior(current, high: integer; var flags: array of Boolean): integer; - {$IFDEF INLINE} inline; {$ENDIF} -begin - Result := current; - if (Result = 0) then Result := high - else dec(Result); - while (Result > 0) and flags[Result] do dec(Result); - if not flags[Result] then Exit; - Result := high; - while flags[Result] do dec(Result); -end; +type + PSimplifyRec = ^TSimplifyRec; + TSimplifyRec = record + pt : TPoint64; + pdSqrd : double; + prev : PSimplifyRec; + next : PSimplifyRec; + isEnd : Boolean; + end; function SimplifyPath(const path: TPath64; - epsilon: double; isClosedPath: Boolean = false): TPath64; + shapeTolerance: double; isOpenPath: Boolean): TPath64; var - i,j, len, high: integer; - curr, prev, start, prev2, next, next2: integer; - epsSqr: double; - flags: array of boolean; - dsq: array of double; + i, highI, minLen: integer; + tolSqrd: double; + srArray: array of TSimplifyRec; + first, last: PSimplifyRec; begin Result := nil; - len := Length(path); - if (len < 4) then Exit;; - high := len -1; - epsSqr := Sqr(epsilon); - SetLength(flags, len); - SetLength(dsq, len); - - curr := 0; - if (isClosedPath) then - begin - dsq[0] := PerpendicDistFromLineSqrd(path[0], path[high], path[1]); - dsq[high] := PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]); - end else - begin - dsq[0] := MaxDouble; - dsq[high] := MaxDouble; - end; + highI := High(path); + if isOpenPath then minLen := 2 else minLen := 3; - for i := 1 to high -1 do - dsq[i] := PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]); + if highI +1 < minLen then Exit; - while true do + SetLength(srArray, highI +1); + with srArray[0] do begin - if (dsq[curr] > epsSqr) then + pt := path[0]; + prev := @srArray[highI]; + next := @srArray[1]; + if isOpenPath then + begin + pdSqrd := MaxDouble; + isEnd := true; + end else begin - start := curr; - repeat - curr := GetNext(curr, high, flags); - until (curr = start) or (dsq[curr] < epsSqr); - if (curr = start) then break; + pdSqrd := PerpendicDistSqrd(path[0], path[highI], path[1]); + isEnd := false; end; + end; - prev := GetPrior(curr, high, flags); - next := GetNext(curr, high, flags); - if (next = prev) then break; - - if (dsq[next] < dsq[curr]) then + with srArray[highI] do + begin + pt := path[highI]; + prev := @srArray[highI-1]; + next := @srArray[0]; + if isOpenPath then begin - flags[next] := true; - next := GetNext(next, high, flags); - next2 := GetNext(next, high, flags); - dsq[curr] := PerpendicDistFromLineSqrd( - path[curr], path[prev], path[next]); - if (next <> high) or isClosedPath then - dsq[next] := PerpendicDistFromLineSqrd( - path[next], path[curr], path[next2]); - curr := next; + pdSqrd := MaxDouble; + isEnd := true; end else begin - flags[curr] := true; - curr := next; - next := GetNext(next, high, flags); - prev2 := GetPrior(prev, high, flags); - dsq[curr] := PerpendicDistFromLineSqrd( - path[curr], path[prev], path[next]); - if (prev <> 0) or isClosedPath then - dsq[prev] := PerpendicDistFromLineSqrd( - path[prev], path[prev2], path[curr]); + pdSqrd := PerpendicDistSqrd(path[highI], path[highI-1], path[0]); + isEnd := false; end; end; - j := 0; - SetLength(Result, len); - for i := 0 to High do - if not flags[i] then + + for i := 1 to highI -1 do + with srArray[i] do begin - Result[j] := path[i]; - inc(j); + pt := path[i]; + prev := @srArray[i-1]; + next := @srArray[i+1]; + pdSqrd := PerpendicDistSqrd(path[i], path[i-1], path[i+1]); + isEnd := false; + end; + + first := @srArray[0]; + last := first.prev; + + tolSqrd := Sqr(shapeTolerance); + while first <> last do + begin + if first.isEnd or (first.pdSqrd > tolSqrd) or + (first.next.pdSqrd < first.pdSqrd) then + begin + first := first.next; + end else + begin + first.prev.next := first.next; + first.next.prev := first.prev; + last := first.prev; + dec(highI); + if last.next = last.prev then break; + last.pdSqrd := + PerpendicDistSqrd(last.pt, last.prev.pt, last.next.pt); + first := last.next; + first.pdSqrd := + PerpendicDistSqrd(first.pt, first.prev.pt, first.next.pt); end; - SetLength(Result, j); + end; + if highI +1 < minLen then Exit; + if isOpenPath then first := @srArray[0]; + SetLength(Result, highI +1); + for i := 0 to HighI do + begin + Result[i] := first.pt; + first := first.next; + end; end; +//------------------------------------------------------------------------------ function SimplifyPaths(const paths: TPaths64; - epsilon: double; isClosedPaths: Boolean = false): TPaths64; + shapeTolerance: double; isOpenPaths: Boolean): TPaths64; var i, len: integer; begin len := Length(paths); SetLength(Result, len); for i := 0 to len -1 do - result[i] := SimplifyPath(paths[i], epsilon, isClosedPaths); + result[i] := SimplifyPath(paths[i], shapeTolerance, isOpenPaths); end; end. + From 562ef2da53e9eddeb638c3f79944aa293b84a0cc Mon Sep 17 00:00:00 2001 From: angusj Date: Mon, 17 Jul 2023 18:58:41 +1000 Subject: [PATCH 26/95] Another minor bugfix in GetIntersectPoint(). (#568) And a minor code tidy. --- .../include/clipper2/clipper.core.h | 2 +- CSharp/Clipper2Lib/Clipper.Core.cs | 18 ++--- CSharp/Clipper2Lib/Clipper2Lib.csproj | 8 -- CSharp/USINGZ.TestApp/Program.cs | 79 +++++++++++++++++++ CSharp/USINGZ.TestApp/UsingZTestApp.csproj | 23 ++++++ CSharp/USINGZ.TestApp/UsingZ_TestApp.sln | 31 ++++++++ CSharp/USINGZ/Clipper2LibZ.csproj | 8 ++ Delphi/Clipper2Lib/Clipper.Core.pas | 4 +- Delphi/Clipper2Lib/Clipper.pas | 9 ++- 9 files changed, 160 insertions(+), 22 deletions(-) create mode 100644 CSharp/USINGZ.TestApp/Program.cs create mode 100644 CSharp/USINGZ.TestApp/UsingZTestApp.csproj create mode 100644 CSharp/USINGZ.TestApp/UsingZ_TestApp.sln diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index 171488f0..bdcb702a 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -693,7 +693,7 @@ namespace Clipper2Lib if (det == 0.0) return false; double t = ((ln1a.x - ln2a.x) * dy2 - (ln1a.y - ln2a.y) * dx2) / det; if (t <= 0.0) ip = ln1a; // ?? check further (see also #568) - else if (t >= 1.0) ip = ln2a; // ?? check further + else if (t >= 1.0) ip = ln1b; // ?? check further else { ip.x = static_cast(ln1a.x + t * dx1); diff --git a/CSharp/Clipper2Lib/Clipper.Core.cs b/CSharp/Clipper2Lib/Clipper.Core.cs index 4f526677..6ab1de6a 100644 --- a/CSharp/Clipper2Lib/Clipper.Core.cs +++ b/CSharp/Clipper2Lib/Clipper.Core.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 July 2023 * +* Date : 17 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core structures and functions for the Clipper Library * @@ -631,18 +631,18 @@ internal static bool GetIntersectPt(Point64 ln1a, double dx1 = (ln1b.X - ln1a.X); double dy2 = (ln2b.Y - ln2a.Y); double dx2 = (ln2b.X - ln2a.X); - double cp = dy1 * dx2 - dy2 * dx1; - if (cp == 0.0) + double det = dy1 * dx2 - dy2 * dx1; + if (det == 0.0) { ip = new Point64(); return false; } - double qx = dx1 * ln1a.Y - dy1 * ln1a.X; - double qy = dx2 * ln2a.Y - dy2 * ln2a.X; - ip = new Point64( - CheckCastInt64((dx1 * qy - dx2 * qx) / cp), - CheckCastInt64((dy1 * qy - dy2 * qx) / cp)); - return (ip.X != Invalid64 && ip.Y != Invalid64); + + double t = ((ln1a.X - ln2a.X) * dy2 - (ln1a.Y - ln2a.Y) * dx2) / det; + if (t <= 0.0) ip = ln1a; + else if (t >= 1.0) ip = ln1b; + else ip = new Point64(ln1a.X + t * dx1, ln1a.Y + t * dy1); + return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/CSharp/Clipper2Lib/Clipper2Lib.csproj b/CSharp/Clipper2Lib/Clipper2Lib.csproj index 505c1e8a..93e544b2 100644 --- a/CSharp/Clipper2Lib/Clipper2Lib.csproj +++ b/CSharp/Clipper2Lib/Clipper2Lib.csproj @@ -25,12 +25,4 @@ TRACE - - - - - - - - diff --git a/CSharp/USINGZ.TestApp/Program.cs b/CSharp/USINGZ.TestApp/Program.cs new file mode 100644 index 00000000..4d290bd7 --- /dev/null +++ b/CSharp/USINGZ.TestApp/Program.cs @@ -0,0 +1,79 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 24 January 2023 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2023 * +* License : http://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +using System.Collections.Generic; +using System.Reflection; +using System.IO; +using System; +using System.Security.Cryptography; +using System.Xml.Linq; +using System.Runtime.InteropServices; +using System.Diagnostics; + +using Clipper2Lib; + +public class Application +{ + public class MyCallbacks + { + public void MyCallback64(Point64 bot1, Point64 top1, + Point64 bot2, Point64 top2, ref Point64 intersectPt) + { + intersectPt.Z = 1; + } + + public void MyCallbackD(PointD bot1, PointD top1, + PointD bot2, PointD top2, ref PointD intersectPt) + { + intersectPt.z = 1; + } + } + + public static void Main() + { + PathsD solution = new PathsD(); + PathsD subject = new PathsD(); + subject.Add(Clipper.MakePath(new double[] { 100, 50, 10, 79, 65, 2, 65, 98, 10, 21 })); + + ClipperD clipperD = new ClipperD(); + MyCallbacks cb = new MyCallbacks(); + clipperD.ZCallback = cb.MyCallbackD; + clipperD.AddSubject(subject); + clipperD.Execute(ClipType.Union, FillRule.NonZero, solution); + + + Console.WriteLine(solution.ToString(0)); + + SvgWriter svg= new SvgWriter(FillRule.NonZero); + SvgUtils.AddSubject(svg, subject); + SvgUtils.AddSolution(svg, solution, false); + + PathsD ellipses = new PathsD(); + for (int i = 0; i < solution[0].Count; i++) + { + if (solution[0][i].z == 1) + ellipses.Add(Clipper.Ellipse( + new PointD(solution[0][i].x, solution[0][i].y), 4)); + } + svg.AddClosedPaths(ellipses, 0x20FF0000, 0xFFFF0000, 1); + svg.SaveToFile("usingz.svg", 300, 300); + OpenFileWithDefaultApp("usingz.svg"); + } + + 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(); + } + +} diff --git a/CSharp/USINGZ.TestApp/UsingZTestApp.csproj b/CSharp/USINGZ.TestApp/UsingZTestApp.csproj new file mode 100644 index 00000000..11464c72 --- /dev/null +++ b/CSharp/USINGZ.TestApp/UsingZTestApp.csproj @@ -0,0 +1,23 @@ + + + + Exe + net6.0 + enable + enable + + + + $(DefineConstants);USINGZ + + + + + + + + + + + + diff --git a/CSharp/USINGZ.TestApp/UsingZ_TestApp.sln b/CSharp/USINGZ.TestApp/UsingZ_TestApp.sln new file mode 100644 index 00000000..2bac2abb --- /dev/null +++ b/CSharp/USINGZ.TestApp/UsingZ_TestApp.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33205.214 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UsingZTestApp", "UsingZTestApp.csproj", "{34A93225-8048-4A5A-8741-BC619C13C7B0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Clipper2LibZ", "..\USINGZ\Clipper2LibZ.csproj", "{C50EB9B5-CE07-49DE-B147-941DBFB4FA4E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {34A93225-8048-4A5A-8741-BC619C13C7B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34A93225-8048-4A5A-8741-BC619C13C7B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34A93225-8048-4A5A-8741-BC619C13C7B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34A93225-8048-4A5A-8741-BC619C13C7B0}.Release|Any CPU.Build.0 = Release|Any CPU + {C50EB9B5-CE07-49DE-B147-941DBFB4FA4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C50EB9B5-CE07-49DE-B147-941DBFB4FA4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C50EB9B5-CE07-49DE-B147-941DBFB4FA4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C50EB9B5-CE07-49DE-B147-941DBFB4FA4E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0E1221D6-32D4-4974-9205-DE97EC5F4937} + EndGlobalSection +EndGlobal diff --git a/CSharp/USINGZ/Clipper2LibZ.csproj b/CSharp/USINGZ/Clipper2LibZ.csproj index f9b0fb7e..15fa9407 100644 --- a/CSharp/USINGZ/Clipper2LibZ.csproj +++ b/CSharp/USINGZ/Clipper2LibZ.csproj @@ -14,4 +14,12 @@ TRACE;DEBUG;USINGZ; + + + + + + + + diff --git a/Delphi/Clipper2Lib/Clipper.Core.pas b/Delphi/Clipper2Lib/Clipper.Core.pas index 4bf5f9b3..4e710bd8 100644 --- a/Delphi/Clipper2Lib/Clipper.Core.pas +++ b/Delphi/Clipper2Lib/Clipper.Core.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 16 July 2023 * +* Date : 17 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library module * @@ -1949,7 +1949,7 @@ function GetIntersectPoint(const ln1a, ln1b, ln2a, ln2b: TPoint64; if not Result then Exit; t := ((ln1a.x-ln2a.x) * dy2 - (ln1a.y-ln2a.y) * dx2) / cp; if t <= 0.0 then ip := ln1a - else if t >= 1.0 then ip := ln2a; + else if t >= 1.0 then ip := ln1b; ip.X := Trunc(ln1a.X + t * dx1); ip.Y := Trunc(ln1a.Y + t * dy1); end; diff --git a/Delphi/Clipper2Lib/Clipper.pas b/Delphi/Clipper2Lib/Clipper.pas index 4fc8d203..73fb3260 100644 --- a/Delphi/Clipper2Lib/Clipper.pas +++ b/Delphi/Clipper2Lib/Clipper.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 16 July 2023 * +* Date : 17 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -814,8 +814,13 @@ function PointInPolygon(const pt: TPoint64; function DistanceSqrd(const pt1, pt2: TPoint64): double; {$IFDEF INLINE} inline; {$ENDIF} +var + x1,y1,x2,y2: double; begin - result := Sqr(double(pt1.X) - pt2.X) + Sqr(double(pt1.Y) - pt2.Y); + // nb: older versions of Delphi don't allow explicit typcasting + x1 := pt1.X; y1 := pt1.Y; + x2 := pt2.X; y2 := pt2.Y; + result := Sqr(x1 - x2) + Sqr(y1 - y2); end; //------------------------------------------------------------------------------ From 866e40201476b9af9fec5ce80de16bf0b03eff7a Mon Sep 17 00:00:00 2001 From: angusj Date: Wed, 19 Jul 2023 13:49:42 +1000 Subject: [PATCH 27/95] Further fixes to Polytree nesting (#590) --- CPP/Clipper2Lib/src/clipper.engine.cpp | 5 ++- .../VariableOffset/VariableOffset.cpp | 44 +++++++++++++++---- .../Clipper2Lib.Examples/InflateDemo/Main.cs | 39 ++++++++++++---- CSharp/Clipper2Lib/Clipper.Engine.cs | 6 +-- Delphi/Clipper2Lib/Clipper.Engine.pas | 7 +-- 5 files changed, 75 insertions(+), 26 deletions(-) diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index d89fa3fc..ea5a4889 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 July 2023 * +* Date : 19 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2842,7 +2842,8 @@ namespace Clipper2Lib { { for (auto split : *splits) { - if(split == outrec || split == outrec->owner) continue; + split = GetRealOutRec(split); + if(!split || split == outrec || split == outrec->owner) continue; else if (split->splits && CheckSplitOwner(outrec, split->splits)) return true; else if (CheckBounds(split) && split->bounds.Contains(outrec->bounds) && Path1InsidePath2(outrec->pts, split->pts)) diff --git a/CPP/Examples/VariableOffset/VariableOffset.cpp b/CPP/Examples/VariableOffset/VariableOffset.cpp index 1571c702..62df56e4 100644 --- a/CPP/Examples/VariableOffset/VariableOffset.cpp +++ b/CPP/Examples/VariableOffset/VariableOffset.cpp @@ -39,11 +39,12 @@ void test1() { Paths64 solution; co.Execute(1.0, solution); + std::string filename = "test1.svg"; SvgWriter svg; SvgAddOpenSubject(svg, subject, FillRule::NonZero); SvgAddSolution(svg, solution, FillRule::NonZero, false); - SvgSaveToFile(svg, "c:\\temp\\tmp1.svg", 400, 400); - System("c:\\temp\\tmp1.svg"); + SvgSaveToFile(svg, filename, 400, 400); + System(filename); } void test2() { @@ -68,11 +69,12 @@ void test2() { Paths64 solution; co.Execute(1.0, solution); + std::string filename = "test2.svg"; SvgWriter svg; SvgAddOpenSubject(svg, subject, FillRule::NonZero); SvgAddSolution(svg, solution, FillRule::NonZero, false); - SvgSaveToFile(svg, "c:\\temp\\tmp2.svg", 400, 400); - System("c:\\temp\\tmp2.svg"); + SvgSaveToFile(svg, filename, 400, 400); + System(filename); } void test3() { @@ -95,11 +97,12 @@ void test3() { Paths64 solution; co.Execute(1.0, solution); + std::string filename = "test3.svg"; SvgWriter svg; SvgAddSubject(svg, subject, FillRule::NonZero); SvgAddSolution(svg, solution, FillRule::NonZero, false); - SvgSaveToFile(svg, "c:\\temp\\tmp3.svg", 400, 400); - System("c:\\temp\\tmp3.svg"); + SvgSaveToFile(svg, filename, 400, 400); + System(filename); } void test4() { @@ -118,14 +121,36 @@ void test4() { //double vertex_angle = std::atan2(vertex_sin_a, vertex_cos_a); //double edge_angle = std::atan2(path_norms[curr_idx].y, path_norms[curr_idx].x); double sin_edge = path_norms[curr_idx].y; - return Sqr(sin_edge) * 3 * scale; } + return Sqr(sin_edge) * 3 * scale; } , solution); + std::string filename = "test4.svg"; SvgWriter svg; SvgAddOpenSubject(svg, subject, FillRule::NonZero); SvgAddSolution(svg, solution, FillRule::NonZero, false); - SvgSaveToFile(svg, "c:\\temp\\tmp4.svg", 400, 400); - System("c:\\temp\\tmp4.svg"); + SvgSaveToFile(svg, filename, 400, 400); + System(filename); +} + +void test5() { + + Paths64 solution; + Paths64 subject = { MakePath({0,0, 20,0, 40,0, 60,0, 80,0, 100,0}) }; + + ClipperOffset co; + co.AddPaths(subject, JoinType::Round, EndType::Butt); + co.Execute( + [](const Path64& path, + const PathD& path_norms, size_t curr_idx, size_t prev_idx) { + return double(curr_idx * curr_idx + 10); } + , solution); + + SvgWriter svg; + std::string filename = "test5.svg"; + SvgAddOpenSubject(svg, subject, FillRule::NonZero); + SvgAddSolution(svg, solution, FillRule::NonZero, false); + SvgSaveToFile(svg, filename, 400, 400); + System(filename); } @@ -135,5 +160,6 @@ int main() { test2(); test3(); test4(); + test5(); return 0; } \ No newline at end of file diff --git a/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs b/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs index 97a28e68..31d8aa7a 100644 --- a/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs +++ b/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 September 2022 * +* Date : 19 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2022 * * License : http://www.boost.org/LICENSE_1_0.txt * @@ -20,13 +20,14 @@ public static void Main() { DoSimpleShapes(); DoRabbit(); + DoVariableOffset(); } public static void DoSimpleShapes() { //triangle offset - with large miter Paths64 p = new() { Clipper.MakePath(new int[] { 30, 150, 60, 350, 0, 350 }) }; - Paths64 pp = new (); + Paths64 pp = new(); pp.AddRange(p); for (int i = 0; i < 5; ++i) @@ -42,7 +43,7 @@ public static void DoSimpleShapes() pp.AddRange(p); //nb: using the ClipperOffest class directly here to control //different join types within the same offset operation - ClipperOffset co = new (); + ClipperOffset co = new(); co.AddPaths(p, JoinType.Square, EndType.Joined); p = Clipper.TranslatePaths(p, 120, 100); pp.AddRange(p); @@ -50,10 +51,11 @@ public static void DoSimpleShapes() co.Execute(20, p); pp.AddRange(p); - SvgWriter svg = new (); + string filename = "../../../inflate.svg"; + SvgWriter svg = new(); SvgUtils.AddSolution(svg, pp, false); - SvgUtils.SaveToFile(svg, "../../../inflate.svg", FillRule.EvenOdd, 800, 600, 20); - ClipperFileIO.OpenFileWithDefaultApp("../../../inflate.svg"); + SvgUtils.SaveToFile(svg, filename, FillRule.EvenOdd, 800, 600, 20); + ClipperFileIO.OpenFileWithDefaultApp(filename); } public static void DoRabbit() @@ -71,10 +73,11 @@ public static void DoRabbit() solution.AddRange(pd); } + string filename = "../../../rabbit.svg"; SvgWriter svg = new (); SvgUtils.AddSolution(svg, solution, false); - SvgUtils.SaveToFile(svg, "../../../rabbit2.svg", FillRule.EvenOdd, 450, 720, 10); - ClipperFileIO.OpenFileWithDefaultApp("../../../rabbit2.svg"); + SvgUtils.SaveToFile(svg, filename, FillRule.EvenOdd, 450, 720, 10); + ClipperFileIO.OpenFileWithDefaultApp(filename); } public static PathsD LoadPathsFromResource(string resourceName) @@ -99,7 +102,25 @@ public static PathsD LoadPathsFromResource(string resourceName) } return result; } - + + public static void DoVariableOffset() + { + Paths64 p = new() { Clipper.MakePath(new int[] { 0,50, 20,50, 40,50, 60,50, 80,50, 100,50 }) }; + Paths64 solution = new(); + 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); + + string filename = "../../../variable_offset.svg"; + SvgWriter svg = new(); + SvgUtils.AddOpenSubject(svg, p); + SvgUtils.AddSolution(svg, solution, true); + SvgUtils.SaveToFile(svg, filename, FillRule.EvenOdd, 500, 500, 60); + ClipperFileIO.OpenFileWithDefaultApp(filename); + } + } //end Application } //namespace diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 53084cfb..1fcc75f5 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 July 2023 * +* Date : 19 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -3022,8 +3022,8 @@ private bool CheckSplitOwner(OutRec outrec, List? splits) { foreach (int i in splits!) { - OutRec? split = _outrecList[i]; - if (split == outrec || split == outrec.owner) continue; + OutRec? split = GetRealOutRec(_outrecList[i]); + if (split == null || split == outrec || split == outrec.owner) continue; if (split!.splits != null && CheckSplitOwner(outrec, split.splits)) return true; if (CheckBounds(split) && split.bounds.Contains(outrec.bounds) && Path1InsidePath2(outrec.pts!, split.pts!)) diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index 24ea8b51..4a0f2593 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 16 July 2023 * +* Date : 19 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -3716,8 +3716,9 @@ function TClipperBase.CheckSplitOwner(outrec: POutRec; const splits: TOutRecArra Result := false; for i := 0 to High(splits) do begin - split := splits[i]; - if (split = outrec) or (split = outrec.owner) then Continue + split := GetRealOutRec(splits[i]); + if not assigned(split) or (split = outrec) or (split = outrec.owner) then + Continue else if Assigned(split.splits) and CheckSplitOwner(outrec, split.splits) then begin From 959f96e9374d0251d744bb9ac451b71e9156bff3 Mon Sep 17 00:00:00 2001 From: angusj Date: Wed, 19 Jul 2023 16:20:15 +1000 Subject: [PATCH 28/95] Temporarily disabled Polytree unit testing. (Still buggy) --- CPP/Tests/TestRandomPaths.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CPP/Tests/TestRandomPaths.cpp b/CPP/Tests/TestRandomPaths.cpp index f8ff1ab5..70bfc2b1 100644 --- a/CPP/Tests/TestRandomPaths.cpp +++ b/CPP/Tests/TestRandomPaths.cpp @@ -77,6 +77,8 @@ TEST(Clipper2Tests, TestRandomPaths) const int64_t area_paths = static_cast(Area(solution)); const int64_t count_paths = solution.size() + solution_open.size(); + +/* TEMPORARILY DISABLE POLYPATH TESTING - STILL IN THE PROCESS OF FIXING Clipper2Lib::PolyTree64 solution_polytree; Clipper2Lib::Paths64 solution_polytree_open; Clipper2Lib::Clipper64 clipper_polytree; @@ -84,11 +86,11 @@ TEST(Clipper2Tests, TestRandomPaths) clipper_polytree.AddOpenSubject(subject_open); clipper_polytree.AddClip(clip); clipper_polytree.Execute(ct, fr, solution_polytree, solution_polytree_open); - const auto solution_polytree_paths = PolyTreeToPaths64(solution_polytree); const int64_t area_polytree = static_cast(Area(solution_polytree_paths)); const int64_t count_polytree = solution_polytree_paths.size() + solution_polytree_open.size(); EXPECT_EQ(area_paths, area_polytree); +*/ // polytree does an additional bounds check on each path // and discards paths with empty bounds, so count_polytree // may on occasions be slightly less than count_paths even From 015d5e72d5ab69e97b0f7d45bcec54c66a1ddbe0 Mon Sep 17 00:00:00 2001 From: Juha Reunanen Date: Wed, 26 Jul 2023 09:44:57 +0300 Subject: [PATCH 29/95] Fix problem that may appear when including Windows.h before clipper headers (#602) * Add test case that fails * Fix problem that may appear when including Windows.h before clipper headers --- CPP/CMakeLists.txt | 1 + CPP/Clipper2Lib/include/clipper2/clipper.core.h | 12 ++++++------ CPP/Tests/TestWindows.cpp | 8 ++++++++ 3 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 CPP/Tests/TestWindows.cpp diff --git a/CPP/CMakeLists.txt b/CPP/CMakeLists.txt index e3afbc81..06986c3b 100644 --- a/CPP/CMakeLists.txt +++ b/CPP/CMakeLists.txt @@ -199,6 +199,7 @@ endif() Tests/TestRandomPaths.cpp Tests/TestRectClip.cpp Tests/TestTrimCollinear.cpp + Tests/TestWindows.cpp ) set(CLIPPER2_TESTS "") # one or both of ClipperTests/ClipperTestsZ diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index bdcb702a..3996e48e 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -257,8 +257,8 @@ namespace Clipper2Lib } else { - left = top = std::numeric_limits::max(); - right = bottom = -std::numeric_limits::max(); + left = top = (std::numeric_limits::max)(); + right = bottom = -(std::numeric_limits::max)(); } } @@ -348,8 +348,8 @@ namespace Clipper2Lib template Rect GetBounds(const Path& path) { - auto xmin = std::numeric_limits::max(); - auto ymin = std::numeric_limits::max(); + auto xmin = (std::numeric_limits::max)(); + auto ymin = (std::numeric_limits::max)(); auto xmax = std::numeric_limits::lowest(); auto ymax = std::numeric_limits::lowest(); for (const auto& p : path) @@ -365,8 +365,8 @@ namespace Clipper2Lib template Rect GetBounds(const Paths& paths) { - auto xmin = std::numeric_limits::max(); - auto ymin = std::numeric_limits::max(); + auto xmin = (std::numeric_limits::max)(); + auto ymin = (std::numeric_limits::max)(); auto xmax = std::numeric_limits::lowest(); auto ymax = std::numeric_limits::lowest(); for (const Path& path : paths) diff --git a/CPP/Tests/TestWindows.cpp b/CPP/Tests/TestWindows.cpp new file mode 100644 index 00000000..73d6d56e --- /dev/null +++ b/CPP/Tests/TestWindows.cpp @@ -0,0 +1,8 @@ +// see: https://github.com/AngusJohnson/Clipper2/issues/601 + +#ifdef WIN32 + +#include +#include "clipper2/clipper.h" + +#endif // WIN32 \ No newline at end of file From 3867aab8043aecced7496d0c2b70a2dc85848a67 Mon Sep 17 00:00:00 2001 From: angusj Date: Sat, 5 Aug 2023 08:11:33 +1000 Subject: [PATCH 30/95] Fixed several bugs in Polytree generation (#590, #600, #607) --- .../include/clipper2/clipper.core.h | 2 +- .../include/clipper2/clipper.engine.h | 5 +- CPP/Clipper2Lib/src/clipper.engine.cpp | 62 +++++++--- CPP/Tests/TestRandomPaths.cpp | 3 +- CSharp/Clipper2Lib/Clipper.Core.cs | 58 +++++----- CSharp/Clipper2Lib/Clipper.Engine.cs | 60 +++++++--- CSharp/Clipper2Lib/Clipper.Offset.cs | 5 +- Delphi/Clipper2Lib/Clipper.Engine.pas | 108 +++++++++++------- 8 files changed, 189 insertions(+), 114 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index 3996e48e..7a32d4c4 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 July 2023 * +* Date : 26 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library structures and functions * diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h index 91569855..5aca121b 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 July 2023 * +* Date : 26 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -92,9 +92,11 @@ namespace Clipper2Lib { OutPt* pts = nullptr; PolyPath* polypath = nullptr; OutRecList* splits = nullptr; + OutRec* recursive_split = nullptr; Rect64 bounds = {}; Path64 path; bool is_open = false; + ~OutRec() { if (splits) delete splits; // nb: don't delete the split pointers @@ -249,7 +251,6 @@ namespace Clipper2Lib { void DoTopOfScanbeam(const int64_t top_y); Active *DoMaxima(Active &e); void JoinOutrecPaths(Active &e1, Active &e2); - void CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec); void FixSelfIntersects(OutRec* outrec); void DoSplitOp(OutRec* outRec, OutPt* splitOp); diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index ea5a4889..2d13a226 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 19 July 2023 * +* Date : 5 August 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -419,6 +419,13 @@ namespace Clipper2Lib { return outrec; } + inline bool IsValidOwner(OutRec* outrec, OutRec* testOwner) + { + // prevent outrec owning itself either directly or indirectly + while (testOwner && testOwner != outrec) testOwner = testOwner->owner; + return !testOwner; + } + inline void UncoupleOutRec(Active ae) { OutRec* outrec = ae.outrec; @@ -548,16 +555,24 @@ namespace Clipper2Lib { else return PointInPolygonResult::IsInside; } - inline Rect64 GetBounds(OutPt* op) + inline Path64 GetCleanPath(OutPt* op) { - Rect64 result(op->pt.x, op->pt.y, op->pt.x, op->pt.y); - OutPt* op2 = op->next; + Path64 result; + OutPt* op2 = op; + while (op2->next != op && + ((op2->pt.x == op2->next->pt.x && op2->pt.x == op2->prev->pt.x) || + (op2->pt.y == op2->next->pt.y && op2->pt.y == op2->prev->pt.y))) op2 = op2->next; + result.push_back(op2->pt); + OutPt* prevOp = op2; + op2 = op2->next; while (op2 != op) { - if (op2->pt.x < result.left) result.left = op2->pt.x; - else if (op2->pt.x > result.right) result.right = op2->pt.x; - if (op2->pt.y < result.top) result.top = op2->pt.y; - else if (op2->pt.y > result.bottom) result.bottom = op2->pt.y; + 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); + prevOp = op2; + } op2 = op2->next; } return result; @@ -567,19 +582,21 @@ namespace Clipper2Lib { { // 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 { - PointInPolygonResult result = PointInOpPolygon(op->pt, op2); + result = PointInOpPolygon(op->pt, op2); if (result == PointInPolygonResult::IsOutside) ++outside_cnt; else if (result == PointInPolygonResult::IsInside) --outside_cnt; op = op->next; } while (op != op1 && std::abs(outside_cnt) < 2); if (std::abs(outside_cnt) > 1) return (outside_cnt < 0); // since path1's location is still equivocal, check its midpoint - Point64 mp = GetBounds(op).MidPoint(); - return PointInOpPolygon(mp, op2) == PointInPolygonResult::IsInside; + Point64 mp = GetBounds(GetCleanPath(op1)).MidPoint(); + Path64 path2 = GetCleanPath(op2); + return PointInPolygon(mp, path2) != PointInPolygonResult::IsOutside; } //------------------------------------------------------------------------------ @@ -774,7 +791,7 @@ namespace Clipper2Lib { { if (!minima_list_sorted_) { - std::sort(minima_list_.begin(), minima_list_.end(), LocMinSorter()); + std::stable_sort(minima_list_.begin(), minima_list_.end(), LocMinSorter()); //#594 minima_list_sorted_ = true; } LocalMinimaList::const_reverse_iterator i; @@ -2166,6 +2183,7 @@ namespace Clipper2Lib { } } + void ClipperBase::ProcessHorzJoins() { for (const HorzJoin& j : horz_join_list_) @@ -2832,8 +2850,8 @@ namespace Clipper2Lib { if (!outrec->bounds.IsEmpty()) return true; CleanCollinear(outrec); if (!outrec->pts || - !BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)) - return false; + !BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)){ + return false;} outrec->bounds = GetBounds(outrec->path); return true; } @@ -2843,9 +2861,14 @@ namespace Clipper2Lib { for (auto split : *splits) { split = GetRealOutRec(split); - if(!split || split == outrec || split == outrec->owner) continue; - else if (split->splits && CheckSplitOwner(outrec, split->splits)) return true; - else if (CheckBounds(split) && split->bounds.Contains(outrec->bounds) && + if(!split || split->recursive_split == outrec) continue; + split->recursive_split = outrec; // prevent infinite loops + + if (split->splits && CheckSplitOwner(outrec, split->splits)) + return true; + else if (CheckBounds(split) && + IsValidOwner(outrec, split) && + split->bounds.Contains(outrec->bounds) && Path1InsidePath2(outrec->pts, split->pts)) { outrec->owner = split; //found in split @@ -3030,8 +3053,11 @@ namespace Clipper2Lib { if (has_open_paths_) open_paths.reserve(outrec_list_.size()); - for (OutRec* outrec : outrec_list_) + // outrec_list_.size() is not static here because + // BuildPathD below can indirectly add additional OutRec //#607 + for (size_t i = 0; i < outrec_list_.size(); ++i) { + OutRec* outrec = outrec_list_[i]; if (!outrec || !outrec->pts) continue; if (outrec->is_open) { diff --git a/CPP/Tests/TestRandomPaths.cpp b/CPP/Tests/TestRandomPaths.cpp index 70bfc2b1..6fb0e8df 100644 --- a/CPP/Tests/TestRandomPaths.cpp +++ b/CPP/Tests/TestRandomPaths.cpp @@ -78,7 +78,6 @@ TEST(Clipper2Tests, TestRandomPaths) const int64_t count_paths = solution.size() + solution_open.size(); -/* TEMPORARILY DISABLE POLYPATH TESTING - STILL IN THE PROCESS OF FIXING Clipper2Lib::PolyTree64 solution_polytree; Clipper2Lib::Paths64 solution_polytree_open; Clipper2Lib::Clipper64 clipper_polytree; @@ -90,7 +89,7 @@ TEST(Clipper2Tests, TestRandomPaths) const int64_t area_polytree = static_cast(Area(solution_polytree_paths)); const int64_t count_polytree = solution_polytree_paths.size() + solution_polytree_open.size(); EXPECT_EQ(area_paths, area_polytree); -*/ + // polytree does an additional bounds check on each path // and discards paths with empty bounds, so count_polytree // may on occasions be slightly less than count_paths even diff --git a/CSharp/Clipper2Lib/Clipper.Core.cs b/CSharp/Clipper2Lib/Clipper.Core.cs index 6ab1de6a..dbea7ef2 100644 --- a/CSharp/Clipper2Lib/Clipper.Core.cs +++ b/CSharp/Clipper2Lib/Clipper.Core.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 17 July 2023 * +* Date : 26 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core structures and functions for the Clipper Library * @@ -145,20 +145,24 @@ public Point64(PointD pt, double scale) { return new Point64(lhs.X - rhs.X, lhs.Y - rhs.Y); } - public override string ToString() + public override readonly string ToString() { return $"{X},{Y} "; // nb: trailing space } #endif - public override bool Equals(object? obj) + public override readonly bool Equals(object? obj) { if (obj != null && obj is Point64 p) return this == p; return false; } - public override int GetHashCode() { return 0; } + public override readonly int GetHashCode() + { + return HashCode.Combine(X, Y); //#599 + } + } public struct PointD @@ -253,7 +257,7 @@ public PointD(double x, double y) this.y = y; } - public string ToString(int precision = 2) + public readonly string ToString(int precision = 2) { return string.Format($"{{0:F{precision}}},{{1:F{precision}}}", x,y); } @@ -271,7 +275,7 @@ public string ToString(int precision = 2) !InternalClipper.IsAlmostZero(lhs.y - rhs.y); } - public override bool Equals(object? obj) + public override readonly bool Equals(object? obj) { if (obj != null && obj is PointD p) return this == p; @@ -280,7 +284,11 @@ public override bool Equals(object? obj) public void Negate() { x = -x; y = -y; } - public override int GetHashCode() { return 0; } + public override readonly int GetHashCode() + { + return HashCode.Combine(x, y); //#599 + } + } public struct Rect64 @@ -320,46 +328,44 @@ public Rect64(Rect64 rec) } public long Width - { - get => right - left; + { readonly get => right - left; set => right = left + value; } public long Height - { - get => bottom - top; + { readonly get => bottom - top; set => bottom = top + value; } - public bool IsEmpty() + public readonly bool IsEmpty() { return bottom <= top || right <= left; } - public Point64 MidPoint() + public readonly Point64 MidPoint() { return new Point64((left + right) /2, (top + bottom)/2); } - public bool Contains(Point64 pt) + public readonly bool Contains(Point64 pt) { return pt.X > left && pt.X < right && pt.Y > top && pt.Y < bottom; } - public bool Contains(Rect64 rec) + public readonly bool Contains(Rect64 rec) { return rec.left >= left && rec.right <= right && rec.top >= top && rec.bottom <= bottom; } - public bool Intersects(Rect64 rec) + public readonly bool Intersects(Rect64 rec) { return (Math.Max(left, rec.left) <= Math.Min(right, rec.right)) && (Math.Max(top, rec.top) <= Math.Min(bottom, rec.bottom)); } - public Path64 AsPath() + public readonly Path64 AsPath() { Path64 result = new Path64(4) { @@ -409,46 +415,44 @@ public RectD(bool isValid) } } public double Width - { - get => right - left; + { readonly get => right - left; set => right = left + value; } public double Height - { - get => bottom - top; + { readonly get => bottom - top; set => bottom = top + value; } - public bool IsEmpty() + public readonly bool IsEmpty() { return bottom <= top || right <= left; } - public PointD MidPoint() + public readonly PointD MidPoint() { return new PointD((left + right) / 2, (top + bottom) / 2); } - public bool Contains(PointD pt) + public readonly bool Contains(PointD pt) { return pt.x > left && pt.x < right && pt.y > top && pt.y < bottom; } - public bool Contains(RectD rec) + public readonly bool Contains(RectD rec) { return rec.left >= left && rec.right <= right && rec.top >= top && rec.bottom <= bottom; } - public bool Intersects(RectD rec) + public readonly bool Intersects(RectD rec) { return (Math.Max(left, rec.left) < Math.Min(right, rec.right)) && (Math.Max(top, rec.top) < Math.Min(bottom, rec.bottom)); } - public PathD AsPath() + public readonly PathD AsPath() { PathD result = new PathD(4) { diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 1fcc75f5..312f2d24 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 19 July 2023 * +* Date : 26 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -108,7 +108,7 @@ public IntersectNode(Point64 pt, Active edge1, Active edge2) internal struct LocMinSorter : IComparer { - public int Compare(LocalMinima locMin1, LocalMinima locMin2) + public readonly int Compare(LocalMinima locMin1, LocalMinima locMin2) { return locMin2.vertex.pt.Y.CompareTo(locMin1.vertex.pt.Y); } @@ -150,11 +150,12 @@ internal class OutRec public Path64 path = new Path64(); public bool isOpen; public List? splits = null; + public OutRec? recursiveSplit; }; internal struct HorzSegSorter : IComparer { - public int Compare(HorzSegment? hs1, HorzSegment? hs2) + public readonly int Compare(HorzSegment? hs1, HorzSegment? hs2) { if (hs1 == null || hs2 == null) return 0; if (hs1.rightOp == null) @@ -625,7 +626,7 @@ private static bool IsMaxima(Active ae) private struct IntersectListSort : IComparer { - public int Compare(IntersectNode a, IntersectNode b) + public readonly int Compare(IntersectNode a, IntersectNode b) { if (a.pt.Y == b.pt.Y) { @@ -723,6 +724,14 @@ private static double AreaTriangle(Point64 pt1, Point64 pt2, Point64 pt3) return outRec; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsValidOwner(OutRec? outRec, OutRec? testOwner) + { + while ((testOwner != null) && (testOwner != outRec)) + testOwner = testOwner.owner; + return testOwner == null; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void UncoupleOutRec(Active ae) { @@ -2590,22 +2599,32 @@ private void ConvertHorzSegsToJoins() } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Rect64 GetBounds(OutPt op) + private static Path64 GetCleanPath(OutPt op) { - Rect64 result = new Rect64(op.pt.X, op.pt.Y, op.pt.X, op.pt.Y); - OutPt op2 = op.next!; + Path64 result = new Path64(); + OutPt op2 = op; + while (op2.next != op && + ((op2.pt.X == op2.next!.pt.X && op2.pt.X == op2.prev.pt.X) || + (op2.pt.Y == op2.next.pt.Y && op2.pt.Y == op2.prev.pt.Y))) op2 = op2.next; + result.Add(op2.pt); + OutPt prevOp = op2; + op2 = op2.next; while (op2 != op) { - if (op2.pt.X < result.left) result.left = op2.pt.X; - else if (op2.pt.X > result.right) result.right = op2.pt.X; - if (op2.pt.Y < result.top) result.top = op2.pt.Y; - else if (op2.pt.Y > result.bottom) result.bottom = op2.pt.Y; - op2 = op2.next!; + 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.Add(op2.pt); + prevOp = op2; + } + op2 = op2.next; } return result; } + private static PointInPolygonResult PointInOpPolygon(Point64 pt, OutPt op) { if (op == op.next || op.prev == op.next) @@ -2676,19 +2695,21 @@ 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 { - PointInPolygonResult result = PointInOpPolygon(op.pt, op2); + result = PointInOpPolygon(op.pt, op2); if (result == PointInPolygonResult.IsOutside) ++outside_cnt; else if (result == PointInPolygonResult.IsInside) --outside_cnt; op = op.next!; } while (op != op1 && Math.Abs(outside_cnt) < 2); if (Math.Abs(outside_cnt) > 1) return (outside_cnt < 0); // since path1's location is still equivocal, check its midpoint - Point64 mp = GetBounds(op).MidPoint(); - return PointInOpPolygon(mp, op2) == PointInPolygonResult.IsInside; + Point64 mp = GetBounds(GetCleanPath(op1)).MidPoint(); + Path64 path2 = GetCleanPath(op2); + return InternalClipper.PointInPolygon(mp, path2) != PointInPolygonResult.IsOutside; } private void ProcessHorzJoins() @@ -3023,10 +3044,13 @@ private bool CheckSplitOwner(OutRec outrec, List? splits) foreach (int i in splits!) { OutRec? split = GetRealOutRec(_outrecList[i]); - if (split == null || split == outrec || split == outrec.owner) continue; + if (split == null || split.recursiveSplit == outrec) continue; + split.recursiveSplit = outrec; //#599 if (split!.splits != null && CheckSplitOwner(outrec, split.splits)) return true; - if (CheckBounds(split) && split.bounds.Contains(outrec.bounds) && - Path1InsidePath2(outrec.pts!, split.pts!)) + if (IsValidOwner(outrec, split) && + CheckBounds(split) && + split.bounds.Contains(outrec.bounds) && + Path1InsidePath2(outrec.pts!, split.pts!)) { outrec.owner = split; //found in split return true; diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index 7c175521..60f10ca4 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -77,8 +77,8 @@ public delegate double DeltaCallback64(Path64 path, #if USINGZ public ClipperBase.ZCallback64? ZCallback { get; set; } #endif - public ClipperOffset(double miterLimit = 2.0, - double arcTolerance = 0.0, bool + public ClipperOffset(double miterLimit = 2.0, + double arcTolerance = 0.0, bool preserveCollinear = false, bool reverseSolution = false) { MiterLimit = miterLimit; @@ -90,7 +90,6 @@ public ClipperOffset(double miterLimit = 2.0, ZCallback = null; #endif } - public void Clear() { _groupList.Clear(); diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index 4a0f2593..69385487 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 19 July 2023 * +* Date : 26 July 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -96,6 +96,7 @@ TOutRec = record pts : POutPt; polypath : TPolyPathBase; splits : TOutRecArray; + recursiveCheck : POutRec; bounds : TRect64; path : TPath64; isOpen : Boolean; @@ -633,6 +634,15 @@ function GetRealOutRec(outRec: POutRec): POutRec; end; //------------------------------------------------------------------------------ +function IsValidOwner(outRec, TestOwner: POutRec): Boolean; + {$IFDEF INLINING} inline; {$ENDIF} +begin + while Assigned(TestOwner) and (outrec <> TestOwner) do + TestOwner := TestOwner.owner; + Result := not Assigned(TestOwner); +end; +//------------------------------------------------------------------------------ + function PtsReallyClose(const pt1, pt2: TPoint64): Boolean; {$IFDEF INLINING} inline; {$ENDIF} begin @@ -816,25 +826,48 @@ function GetMaximaPair(e: PActive): PActive; end; //------------------------------------------------------------------------------ -function GetBounds(op: POutPt): TRect64; +function PointCount(pts: POutPt): Integer; {$IFDEF INLINING} inline; {$ENDIF} var - op2: POutPt; + p: POutPt; begin - result.Left := op.pt.X; - result.Right := op.pt.X; - result.Top := op.pt.Y; - result.Bottom := op.pt.Y; - op2 := op.next; - while op2 <> op do + Result := 0; + if not Assigned(pts) then Exit; + p := pts; + repeat + Inc(Result); + p := p.next; + until p = pts; +end; +//------------------------------------------------------------------------------ + +function GetCleanPath(op: POutPt): TPath64; +var + cnt: integer; + op2, prevOp: POutPt; +begin + cnt := 0; + SetLength(Result, PointCount(op)); + op2 := op; + while ((op2.next <> op) and + (((op2.pt.X = op2.next.pt.X) and (op2.pt.X = op2.prev.pt.X)) or + ((op2.pt.Y = op2.next.pt.Y) and (op2.pt.Y = op2.prev.pt.Y)))) do + op2 := op2.next; + result[cnt] := op2.pt; + inc(cnt); + prevOp := op2; + op2 := op2.next; + while (op2 <> op) do begin - if op2.pt.X < result.Left then result.Left := op2.pt.X - else if op2.pt.X > result.Right then result.Right := op2.pt.X; - if op2.pt.Y < result.Top then result.Top := op2.pt.Y - else if op2.pt.Y > result.Bottom then result.Bottom := op2.pt.Y; + if (((op2.pt.X <> op2.next.pt.X) or (op2.pt.X <> prevOp.pt.X)) and + ((op2.pt.Y <> op2.next.pt.Y) or (op2.pt.Y <> prevOp.pt.Y))) then + begin + result[cnt] := op2.pt; + prevOp := op2; + end; op2 := op2.next; end; + SetLength(Result, cnt); end; -//------------------------------------------------------------------------------ function PointInOpPolygon(const pt: TPoint64; op: POutPt): TPointInPolygonResult; var @@ -909,6 +942,8 @@ function PointInOpPolygon(const pt: TPoint64; op: POutPt): TPointInPolygonResult function Path1InsidePath2(const op1, op2: POutPt): Boolean; var op: POutPt; + mp: TPoint64; + path: TPath64; pipResult: TPointInPolygonResult; outsideCnt: integer; begin @@ -924,23 +959,10 @@ function Path1InsidePath2(const op1, op2: POutPt): Boolean; op := op.next; until (op = op1) or (Abs(outsideCnt) = 2); // if path1's location is still equivocal then check its midpoint - if Abs(outsideCnt) > 1 then - Result := outsideCnt < 0 else - Result := PointInOpPolygon(GetBounds(op).MidPoint, op2) = pipInside; -end; -//------------------------------------------------------------------------------ - -function PointCount(pts: POutPt): Integer; {$IFDEF INLINING} inline; {$ENDIF} -var - p: POutPt; -begin - Result := 0; - if not Assigned(pts) then Exit; - p := pts; - repeat - Inc(Result); - p := p.next; - until p = pts; + path := GetCleanPath(op1); + mp := Clipper.Core.GetBounds(path).MidPoint; + path := GetCleanPath(op2); + Result := PointInPolygon(mp, path) <> pipOutside; end; //------------------------------------------------------------------------------ @@ -3713,27 +3735,27 @@ function TClipperBase.CheckSplitOwner(outrec: POutRec; const splits: TOutRecArra i : integer; split : POutrec; begin - Result := false; + // returns true if a valid owner is found in splits + // (and also assigns it to outrec.owner) + Result := true; for i := 0 to High(splits) do begin split := GetRealOutRec(splits[i]); - if not assigned(split) or (split = outrec) or (split = outrec.owner) then - Continue - else if Assigned(split.splits) and - CheckSplitOwner(outrec, split.splits) then - begin - Result := True; - Exit; - end - else if CheckBounds(split) and + if (split = nil) or (split.recursiveCheck = outrec) then Continue; + split.recursiveCheck := outrec; // prevent infinite loops + + if Assigned(split.splits) and + CheckSplitOwner(outrec, split.splits) then Exit + else if IsValidOwner(outrec, split) and + CheckBounds(split) and (split.bounds.Contains(outrec.bounds) and - Path1InsidePath2(outrec.pts, split.pts)) then + Path1InsidePath2(outrec.pts, split.pts)) then begin outrec.owner := split; - Result := True; Exit; end; end; + Result := false; end; //------------------------------------------------------------------------------ From 6222af46c73ab392e0b6189935304c3c9b6dfcc8 Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 6 Aug 2023 12:56:30 +1000 Subject: [PATCH 31/95] Fixed a minor bug in RectClip (#597) Fixed a minor bug in merging solution polygons (#606) --- CPP/Clipper2Lib/src/clipper.engine.cpp | 4 +--- CPP/Clipper2Lib/src/clipper.rectclip.cpp | 4 ++-- CPP/Tests/TestRectClip.cpp | 11 +++++++++++ CSharp/Clipper2Lib/Clipper.Engine.cs | 8 ++------ CSharp/Clipper2Lib/Clipper.RectClip.cs | 4 ++-- Delphi/Clipper2Lib/Clipper.Engine.pas | 7 ++----- Delphi/Clipper2Lib/Clipper.RectClip.pas | 4 ++-- 7 files changed, 22 insertions(+), 20 deletions(-) diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index 2d13a226..99a734d9 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -2499,7 +2499,6 @@ namespace Clipper2Lib { #endif AddTrialHorzJoin(op); } - OutRec* currHorzOutrec = horz.outrec; while (true) // loop through consec. horizontal edges { @@ -2579,9 +2578,8 @@ namespace Clipper2Lib { e = horz.prev_in_ael; } - if (horz.outrec && horz.outrec != currHorzOutrec) + if (horz.outrec) { - currHorzOutrec = horz.outrec; //nb: The outrec containining the op returned by IntersectEdges //above may no longer be associated with horzEdge. AddTrialHorzJoin(GetLastOp(horz)); diff --git a/CPP/Clipper2Lib/src/clipper.rectclip.cpp b/CPP/Clipper2Lib/src/clipper.rectclip.cpp index 1fad1899..221cd9bb 100644 --- a/CPP/Clipper2Lib/src/clipper.rectclip.cpp +++ b/CPP/Clipper2Lib/src/clipper.rectclip.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 July 2023 * +* Date : 6 August 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -474,7 +474,7 @@ namespace Clipper2Lib { // intersect pt but we'll also need the first intersect pt (ip2) loc = prev; GetIntersection(rect_as_path_, prev_pt, path[i], loc, ip2); - if (crossing_prev != Location::Inside) + if (crossing_prev != Location::Inside && crossing_prev != loc) //579 AddCorner(crossing_prev, loc); if (first_cross_ == Location::Inside) diff --git a/CPP/Tests/TestRectClip.cpp b/CPP/Tests/TestRectClip.cpp index 2e8628d2..949ea979 100644 --- a/CPP/Tests/TestRectClip.cpp +++ b/CPP/Tests/TestRectClip.cpp @@ -55,4 +55,15 @@ TEST(Clipper2Tests, TestRectClip) EXPECT_EQ(solBounds.Width(), rect.Width()); EXPECT_EQ(solBounds.Height(), rect.Height()); +} + +TEST(Clipper2Tests, TestRectClip2) //#597 +{ + Clipper2Lib::Rect64 rect(54690, 0, 65628, 6000); + Clipper2Lib::Paths64 subject {{{700000, 6000}, { 0, 6000 }, { 0, 5925 }, { 700000, 5925 }}}; + + Clipper2Lib::Paths64 solution = Clipper2Lib::RectClip(rect, subject); + + //std::cout << solution << std::endl; + EXPECT_TRUE(solution.size() == 1 && solution[0].size() == 4); } \ No newline at end of file diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 312f2d24..54864bde 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 July 2023 * +* Date : 6 August 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2173,7 +2173,6 @@ private void DoHorizontal(Active horz) #endif AddToHorzSegList(op); } - OutRec? currOutrec = horz.outrec!; for (; ; ) { @@ -2248,11 +2247,8 @@ private void DoHorizontal(Active horz) ae = horz.prevInAEL; } - if (IsHotEdge(horz) && (horz.outrec != currOutrec)) - { - currOutrec = horz.outrec; + if (IsHotEdge(horz)) AddToHorzSegList(GetLastOp(horz)); - } } // we've reached the end of this horizontal diff --git a/CSharp/Clipper2Lib/Clipper.RectClip.cs b/CSharp/Clipper2Lib/Clipper.RectClip.cs index 5e3097f9..a570c67d 100644 --- a/CSharp/Clipper2Lib/Clipper.RectClip.cs +++ b/CSharp/Clipper2Lib/Clipper.RectClip.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 30 May 2023 * +* Date : 6 August 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -544,7 +544,7 @@ private void ExecuteInternal(Path64 path) loc = prev; GetIntersection(rectPath_, prevPt, path[i], ref loc, out Point64 ip2); - if (prevCrossLoc != Location.inside) + if (prevCrossLoc != Location.inside && prevCrossLoc != loc) //#597 AddCorner(prevCrossLoc, loc); if (firstCross == Location.inside) diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index 69385487..d312731f 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 26 July 2023 * +* Date : 6 August 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -3401,7 +3401,6 @@ procedure TClipperBase.DoHorizontal(horzEdge: PActive); e: PActive; pt: TPoint64; op: POutPt; - currOr: POutRec; isLeftToRight, horzIsOpen: Boolean; begin (******************************************************************************* @@ -3443,7 +3442,6 @@ procedure TClipperBase.DoHorizontal(horzEdge: PActive); {$ENDIF} FHorzSegList.Add(op); end; - currOr := horzEdge.outrec; while true do // loop through consec. horizontal edges begin @@ -3520,9 +3518,8 @@ procedure TClipperBase.DoHorizontal(horzEdge: PActive); horzEdge.currX := e.currX; e := horzEdge.prevInAEL; end; - if IsHotEdge(horzEdge) and (horzEdge.outrec <> currOr) then + if IsHotEdge(horzEdge) then begin - currOr := horzEdge.outrec; //nb: The outrec containining the op returned by IntersectEdges //above may no longer be associated with horzEdge. FHorzSegList.Add(GetLastOp(horzEdge)); diff --git a/Delphi/Clipper2Lib/Clipper.RectClip.pas b/Delphi/Clipper2Lib/Clipper.RectClip.pas index 3ebc0a78..29b3c9bb 100644 --- a/Delphi/Clipper2Lib/Clipper.RectClip.pas +++ b/Delphi/Clipper2Lib/Clipper.RectClip.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 30 May 2023 * +* Date : 6 August 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -700,7 +700,7 @@ procedure TRectClip64.ExecuteInternal(const path: TPath64); // intersect pt but we'll also need the first intersect pt (ip2) loc := prevLoc; GetIntersection(fRectPath, prevPt, path[i], loc, ip2); - if (prevCrossLoc <> locInside) then + if (prevCrossLoc <> locInside) and (prevCrossLoc <> loc) then //#579 AddCorner(prevCrossLoc, loc); if (firstCrossLoc = locInside) then From 739f2e32e8e82ba2e86bd1e479ec3a029951f316 Mon Sep 17 00:00:00 2001 From: angusj Date: Mon, 7 Aug 2023 08:16:47 +1000 Subject: [PATCH 32/95] Minor bugfix in polygon offsetting (#593) --- CPP/Clipper2Lib/src/clipper.offset.cpp | 13 ++++++++++++- CSharp/Clipper2Lib/Clipper.Offset.cs | 12 +++++++++++- Delphi/Clipper2Lib/Clipper.Offset.pas | 14 +++++++++++++- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index 017976c7..5faa2617 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 July 2023 * +* Date : 7 August 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -354,6 +354,17 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) void ClipperOffset::OffsetPolygon(Group& group, Path64& path) { + // when the path is contracting, make sure + // there is sufficient space to do so. //#593 + // nb: this will have a small impact on performance + double a = Area(path); + // contracting when orientation is opposite offset direction + if ((a < 0) != (group_delta_ < 0)) + { + Rect64 rec = GetBounds(path); + if (std::fabs(group_delta_) * 2 > rec.Width()) return; + } + for (Path64::size_type j = 0, k = path.size() -1; j < path.size(); k = j, ++j) OffsetPoint(group, path, j, k); group.paths_out.push_back(group.path); diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index 60f10ca4..d90c95ea 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 July 2023 * +* Date : 7 August 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -498,6 +498,16 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void OffsetPolygon(Group group, Path64 path) { + // when the path is contracting, make sure + // there is sufficient space to do so. //#593 + //nb: this will have a small impact on performance + double a = Clipper.Area(path); + if ((a < 0) != (_groupDelta < 0)) + { + Rect64 rec = Clipper.GetBounds(path); + if (Math.Abs(_groupDelta) * 2 > rec.Width) return; + } + group.outPath = new Path64(); int cnt = path.Count, prev = cnt - 1; for (int i = 0; i < cnt; i++) diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index ed406869..b02b936b 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 16 July 2023 * +* Date : 7 August 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -429,7 +429,19 @@ procedure TClipperOffset.BuildNormals; procedure TClipperOffset.OffsetPolygon; var i,j: integer; + a: double; + rec: TRect64; begin + //when the path is contracting, make sure + //there is sufficient space to do so. //#593 + //nb: this will have a small impact on performance + a := Area(fInPath); + if (a < 0) <> (fGroupDelta < 0) then + begin + rec := GetBounds(fInPath); + if Abs(fGroupDelta) * 2 >= rec.Width then Exit; + end; + j := high(fInPath); for i := 0 to high(fInPath) do OffsetPoint(i, j); From 4ef91e99756f97cab23e7fa2cd78861deeb61338 Mon Sep 17 00:00:00 2001 From: Angus Johnson Date: Mon, 14 Aug 2023 18:20:02 +1000 Subject: [PATCH 33/95] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 01c7e589..0e57a8b9 100644 --- a/README.md +++ b/README.md @@ -46,5 +46,5 @@ Delphi: Compiles with any version of Delphi back to Delphi 7. ### Ports to other languages **Java**: https://github.com/micycle1/Clipper2-java/
-**Kotlin**: https://github.com/Monkey-Maestro/clipper2-kotlin - +**Kotlin**: https://github.com/Monkey-Maestro/clipper2-kotlin
+**golang**: https://github.com/epit3d/goclipper2 From 7cf615c2799a9328ec32eb116f46b64ed34aecff Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 27 Aug 2023 14:43:26 +1000 Subject: [PATCH 34/95] Another fix to a persistent bug in polytree nesting (#618) --- CPP/CMakeLists.txt | 5 +- CPP/Clipper2Lib/src/clipper.engine.cpp | 42 +++- CPP/Tests/TestPolytreeHoles.cpp | 253 +++++++++++++++++++++++++ CPP/Tests/TestPolytreeHoles1.cpp | 31 --- CPP/Tests/TestPolytreeHoles2.cpp | 132 ------------- CPP/Tests/TestPolytreeHoles3.cpp | 25 --- CPP/Tests/TestPolytreeHoles4.cpp | 26 --- CSharp/Clipper2Lib/Clipper.Engine.cs | 40 +++- Delphi/Clipper2Lib/Clipper.Engine.pas | 54 ++++-- 9 files changed, 357 insertions(+), 251 deletions(-) create mode 100644 CPP/Tests/TestPolytreeHoles.cpp delete mode 100644 CPP/Tests/TestPolytreeHoles1.cpp delete mode 100644 CPP/Tests/TestPolytreeHoles2.cpp delete mode 100644 CPP/Tests/TestPolytreeHoles3.cpp delete mode 100644 CPP/Tests/TestPolytreeHoles4.cpp diff --git a/CPP/CMakeLists.txt b/CPP/CMakeLists.txt index 06986c3b..84f8e263 100644 --- a/CPP/CMakeLists.txt +++ b/CPP/CMakeLists.txt @@ -190,10 +190,7 @@ endif() Tests/TestOffsetOrientation.cpp Tests/TestOrientation.cpp Tests/TestPolygons.cpp - Tests/TestPolytreeHoles1.cpp - Tests/TestPolytreeHoles2.cpp - Tests/TestPolytreeHoles3.cpp - Tests/TestPolytreeHoles4.cpp + Tests/TestPolytreeHoles.cpp Tests/TestPolytreeIntersection.cpp Tests/TestPolytreeUnion.cpp Tests/TestRandomPaths.cpp diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index 99a734d9..bed48b15 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 5 August 2023 * +* Date : 27 August 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2183,6 +2183,16 @@ namespace Clipper2Lib { } } + void MoveSplits(OutRec* fromOr, OutRec* toOr) + { + if (!fromOr->splits) return; + if (!toOr->splits) toOr->splits = new OutRecList(); + OutRecList::iterator orIter = fromOr->splits->begin(); + for (; orIter != fromOr->splits->end(); ++orIter) + toOr->splits->push_back(*orIter); + fromOr->splits->clear(); + } + void ClipperBase::ProcessHorzJoins() { @@ -2203,25 +2213,36 @@ namespace Clipper2Lib { or2 = NewOutRec(); or2->pts = op1b; FixOutRecPts(or2); + + //if or1->pts has moved to or2 then update or1->pts!! if (or1->pts->outrec == or2) { or1->pts = j.op1; or1->pts->outrec = or1; } - if (using_polytree_) //#498, #520, #584, D#576 + if (using_polytree_) //#498, #520, #584, D#576, #618 { if (Path1InsidePath2(or1->pts, or2->pts)) { - or2->owner = or1->owner; - SetOwner(or1, or2); + //swap or1's & or2's pts + OutPt* tmp = or1->pts; + or1->pts = or2->pts; + or2->pts = tmp; + FixOutRecPts(or1); + FixOutRecPts(or2); + //or2 is now inside or1 + or2->owner = or1; } - else + else if (Path1InsidePath2(or2->pts, or1->pts)) { - SetOwner(or2, or1); - if (!or1->splits) or1->splits = new OutRecList(); - or1->splits->push_back(or2); + or2->owner = or1; } + else + or2->owner = or1->owner; + + if (!or1->splits) or1->splits = new OutRecList(); + or1->splits->push_back(or2); } else or2->owner = or1; @@ -2230,7 +2251,10 @@ namespace Clipper2Lib { { or2->pts = nullptr; if (using_polytree_) + { SetOwner(or2, or1); + MoveSplits(or2, or1); //#618 + } else or2->owner = or1; } @@ -2859,7 +2883,7 @@ namespace Clipper2Lib { for (auto split : *splits) { split = GetRealOutRec(split); - if(!split || split->recursive_split == outrec) continue; + if(!split || split == outrec || split->recursive_split == outrec) continue; split->recursive_split = outrec; // prevent infinite loops if (split->splits && CheckSplitOwner(outrec, split->splits)) diff --git a/CPP/Tests/TestPolytreeHoles.cpp b/CPP/Tests/TestPolytreeHoles.cpp new file mode 100644 index 00000000..ed7a0a57 --- /dev/null +++ b/CPP/Tests/TestPolytreeHoles.cpp @@ -0,0 +1,253 @@ +#include +#include "clipper2/clipper.h" +#include "ClipFileLoad.h" + +using namespace Clipper2Lib; + +TEST(Clipper2Tests, TestPolytreeHoles1) +{ + std::ifstream ifs("PolytreeHoleOwner.txt"); + ASSERT_TRUE(ifs); + ASSERT_TRUE(ifs.good()); + + Paths64 subject, subject_open, clip; + PolyTree64 solution; + Paths64 solution_open; + ClipType ct = ClipType::None; + FillRule fr = FillRule::EvenOdd; + int64_t area = 0, count = 0; + + bool success = false; + ASSERT_TRUE(LoadTestNum(ifs, 1, subject, subject_open, clip, area, count, ct, fr)); + + Clipper64 c; + c.AddSubject(subject); + c.AddOpenSubject(subject_open); + c.AddClip(clip); + c.Execute(ct, fr, solution, solution_open); + + EXPECT_TRUE(CheckPolytreeFullyContainsChildren(solution)); +} + + +void PolyPathContainsPoint(const PolyPath64& pp, const Point64 pt, int& counter) +{ + if (pp.Polygon().size() > 0) + { + if (PointInPolygon(pt, pp.Polygon()) != PointInPolygonResult::IsOutside) + { + if (pp.IsHole()) --counter; + else ++counter; + } + } + for (const auto& child : pp) + PolyPathContainsPoint(*child, pt, counter); +} + +bool PolytreeContainsPoint(const PolyPath64& pp, const Point64 pt) +{ + int counter = 0; + for (const auto& child : pp) + PolyPathContainsPoint(*child, pt, counter); + EXPECT_GE(counter, 0); //ie 'pt' can't be inside more holes than outers + return counter != 0; +} + +void GetPolyPathArea(const PolyPath64& pp, double& area) +{ + area += Area(pp.Polygon()); + for (const auto& child : pp) + GetPolyPathArea(*child, area); +} + +double GetPolytreeArea(const PolyPath64& pp) +{ + double result = 0; + for (const auto& child : pp) + GetPolyPathArea(*child, result); + return result; +} + +TEST(Clipper2Tests, TestPolytreeHoles2) +{ + std::ifstream ifs("PolytreeHoleOwner2.txt"); + ASSERT_TRUE(ifs); + ASSERT_TRUE(ifs.good()); + + Paths64 subject, subject_open, clip; + ClipType ct = ClipType::None; + FillRule fr = FillRule::EvenOdd; + int64_t area = 0, count = 0; + + ASSERT_TRUE(LoadTestNum(ifs, 1, subject, subject_open, clip, area, count, ct, fr)); + + const std::vector points_of_interest_outside = { + Point64(21887, 10420), + Point64(21726, 10825), + Point64(21662, 10845), + Point64(21617, 10890) + }; + + // confirm that each 'points_of_interest_outside' is outside every subject, + for (const auto& poi_outside : points_of_interest_outside) + { + int outside_subject_count = 0; + for (const auto& path : subject) + if (PointInPolygon(poi_outside, path) != PointInPolygonResult::IsOutside) + ++outside_subject_count; + EXPECT_EQ(outside_subject_count, 0); + } + + const std::vector points_of_interest_inside = { + Point64(21887, 10430), + Point64(21843, 10520), + Point64(21810, 10686), + Point64(21900, 10461) + }; + + // confirm that each 'points_of_interest_inside' is inside a subject, + // and inside only one subject (to exclude possible subject holes) + for (const auto& poi_inside : points_of_interest_inside) + { + int inside_subject_count = 0; + for (const auto& path : subject) + { + if (PointInPolygon(poi_inside, path) != PointInPolygonResult::IsOutside) + ++inside_subject_count; + } + EXPECT_EQ(inside_subject_count, 1); + } + + PolyTree64 solution_tree; + Paths64 solution_open; + Clipper64 c; + c.AddSubject(subject); + c.AddOpenSubject(subject_open); + c.AddClip(clip); + c.Execute(ct, FillRule::Negative, solution_tree, solution_open); + + const auto solution_paths = PolyTreeToPaths64(solution_tree); + + ASSERT_FALSE(solution_paths.empty()); + + const double subject_area = -Area(subject); //negate (see fillrule) + const double solution_tree_area = GetPolytreeArea(solution_tree); + const double solution_paths_area = Area(solution_paths); + + // 1a. check solution_paths_area is smaller than subject_area + EXPECT_LT(solution_paths_area, subject_area); + // 1b. but not too much smaller + EXPECT_GT(solution_paths_area, (subject_area * 0.92)); + + // 2. check solution_tree's area matches solution_paths' area + EXPECT_NEAR(solution_tree_area, solution_paths_area, 0.0001); + + // 3. check that all children are inside their parents + EXPECT_TRUE(CheckPolytreeFullyContainsChildren(solution_tree)); + + // 4. confirm all 'point_of_interest_outside' are outside polytree + for (const auto& poi_outside : points_of_interest_outside) + EXPECT_FALSE(PolytreeContainsPoint(solution_tree, poi_outside)); + + // 5. confirm all 'point_of_interest_inside' are inside polytree + for (const auto& poi_inside : points_of_interest_inside) + EXPECT_TRUE(PolytreeContainsPoint(solution_tree, poi_inside)); + +} + +TEST(Clipper2Tests, TestPolytreeHoles3) +{ + + Paths64 subject, clip, sol; + PolyTree64 solution; + Clipper64 c; + subject.push_back(MakePath({ 1072,501, 1072,501, 1072,539, 1072,539, 1072,539, 870,539, + 870,539, 870,539, 870,520, 894,520, 898,524, 911,524, 915,520, 915,520, 936,520, + 940,524, 953,524, 957,520, 957,520, 978,520, 983,524, 995,524, 1000,520, 1021,520, + 1025,524, 1038,524, 1042,520, 1038,516, 1025,516, 1021,520, 1000,520, 995,516, + 983,516, 978,520, 957,520, 953,516, 940,516, 936,520, 915,520, 911,516, 898,516, + 894,520, 870,520, 870,516, 870,501, 870,501, 870,501, 1072,501 })); + + clip.push_back(MakePath({ 870,501, 971,501, 971,539, 870,539 })); + + c.AddSubject(subject); + c.AddClip(clip); + c.Execute(ClipType::Intersection, FillRule::NonZero, solution); + EXPECT_TRUE(solution.Count() == 1 && solution[0]->Count() == 2); +} + +TEST(Clipper2Tests, TestPolytreeHoles4) //#618 +{ + Paths64 subject; + PolyTree64 solution; + Clipper64 c; + subject.push_back(MakePath({ 50,500, 50,300, 100,300, 100,350, 150,350, + 150,250, 200,250, 200,450, 350,450, 350,200, 400,200, 400,225, 450,225, + 450,175, 400,175, 400,200, 350,200, 350,175, 200,175, 200,250, 150,250, + 150,200, 100,200, 100,300, 50,300, 50,125, 500,125, 500,500 })); + subject.push_back(MakePath({ 250,425, 250,375, 300,375, 300,425 })); + + c.AddSubject(subject); + c.Execute(ClipType::Union, FillRule::NonZero, solution); + // Polytree root + // +- Polygon with 3 holes. + // +- Hole with 1 nested polygon. + // +-Polygon + // +- Hole + // +- Hole + EXPECT_TRUE(solution.Count() == 1 && solution[0]->Count() == 3); +} + +TEST(Clipper2Tests, TestPolytreeHoles5) +{ + Paths64 subject, clip; + subject.push_back(MakePath({ 0,30, 400,30, 400,100, 0,100 })); + clip.push_back(MakePath({ 20,30, 30,30, 30,150, 20,150 })); + clip.push_back(MakePath({ 200,0, 300,0, 300,30, 280,30, 280,20, 220,20, 220,30, 200,30 })); + clip.push_back(MakePath({ 200,50, 300,50, 300,80, 200,80 })); + + Clipper64 c; + c.AddSubject(subject); + c.AddClip(clip); + + PolyTree64 tree; + c.Execute(ClipType::Xor, FillRule::NonZero, tree); + + ////std::cout << tree << std::endl; + //Polytree with 3 polygons. + // + -Polygon (2) contains 2 holes. + + EXPECT_TRUE(tree.Count() == 3 && tree[2]->Count() == 2); + +} + + +TEST(Clipper2Tests, TestPolytreeHoles6) //#618 +{ + Paths64 subject, clip; + subject.push_back(MakePath({ 150,50, 200,50, 200,100, 150,100 })); + subject.push_back(MakePath({ 125,100, 150,100, 150,150, 125,150 })); + subject.push_back(MakePath({ 225,50, 300,50, 300,80, 225,80 })); + subject.push_back(MakePath({ 225,100, 300,100, 300,150, 275,150, 275,175, 260,175, + 260,250, 235,250, 235,300, 275,300, 275,275, 300,275, 300,350, 225,350 })); + subject.push_back(MakePath({ 300,150, 350,150, 350,175, 300,175 })); + clip.push_back(MakePath({ 0,0, 400,0, 400,50, 0,50 })); + clip.push_back(MakePath({ 0,100, 400,100, 400,150, 0,150 })); + clip.push_back(MakePath({ 260,175, 325,175, 325,275, 260,275 })); + + Clipper64 c; + c.AddSubject(subject); + c.AddClip(clip); + + PolyTree64 tree; + c.Execute(ClipType::Xor, FillRule::NonZero, tree); + + ////std::cout << tree << std::endl; + //Polytree with 3 polygons. + // + -Polygon (2) contains 1 holes. + + EXPECT_TRUE(tree.Count() == 3 && tree[2]->Count() == 1); + +} + + diff --git a/CPP/Tests/TestPolytreeHoles1.cpp b/CPP/Tests/TestPolytreeHoles1.cpp deleted file mode 100644 index 176ef063..00000000 --- a/CPP/Tests/TestPolytreeHoles1.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include "clipper2/clipper.h" -#include "ClipFileLoad.h" - -using namespace Clipper2Lib; - -TEST(Clipper2Tests, TestPolytreeHoles1) -{ - std::ifstream ifs("PolytreeHoleOwner.txt"); - ASSERT_TRUE(ifs); - ASSERT_TRUE(ifs.good()); - - Paths64 subject, subject_open, clip; - PolyTree64 solution; - Paths64 solution_open; - ClipType ct; - FillRule fr; - int64_t area, count; - - bool success = false; - ASSERT_TRUE(LoadTestNum(ifs, 1, subject, subject_open, clip, area, count, ct, fr)); - - Clipper64 c; - c.AddSubject(subject); - c.AddOpenSubject(subject_open); - c.AddClip(clip); - c.Execute(ct, fr, solution, solution_open); - - EXPECT_TRUE(CheckPolytreeFullyContainsChildren(solution)); - -} diff --git a/CPP/Tests/TestPolytreeHoles2.cpp b/CPP/Tests/TestPolytreeHoles2.cpp deleted file mode 100644 index aa2c5632..00000000 --- a/CPP/Tests/TestPolytreeHoles2.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include -#include "clipper2/clipper.h" -#include "ClipFileLoad.h" - -using namespace Clipper2Lib; - - -void PolyPathContainsPoint(const PolyPath64& pp, const Point64 pt, int& counter) -{ - if (pp.Polygon().size() > 0) - { - if (PointInPolygon(pt, pp.Polygon()) != PointInPolygonResult::IsOutside) - { - if (pp.IsHole()) --counter; - else ++counter; - } - } - for (const auto& child : pp) - PolyPathContainsPoint(*child, pt, counter); -} - -bool PolytreeContainsPoint(const PolyPath64& pp, const Point64 pt) -{ - int counter = 0; - for (const auto& child : pp) - PolyPathContainsPoint(*child, pt, counter); - EXPECT_GE(counter, 0); //ie 'pt' can't be inside more holes than outers - return counter != 0; -} - -void GetPolyPathArea(const PolyPath64& pp, double& area) -{ - area += Area(pp.Polygon()); - for (const auto& child : pp) - GetPolyPathArea(*child, area); -} - -double GetPolytreeArea(const PolyPath64& pp) -{ - double result = 0; - for (const auto& child : pp) - GetPolyPathArea(*child, result); - return result; -} - -TEST(Clipper2Tests, TestPolytreeHoles2) -{ - std::ifstream ifs("PolytreeHoleOwner2.txt"); - //if (!ifs.good()) return; - ASSERT_TRUE(ifs); - ASSERT_TRUE(ifs.good()); - - Paths64 subject, subject_open, clip; - ClipType ct = ClipType::None; - FillRule fr = FillRule::EvenOdd; - int64_t area = 0, count = 0; - - ASSERT_TRUE(LoadTestNum(ifs, 1, subject, subject_open, clip, area, count, ct, fr)); - - const std::vector points_of_interest_outside = { - Point64(21887, 10420), - Point64(21726, 10825), - Point64(21662, 10845), - Point64(21617, 10890) - }; - - // confirm that each 'points_of_interest_outside' is outside every subject, - for (const auto& poi_outside : points_of_interest_outside) - { - int outside_subject_count = 0; - for (const auto& path : subject) - if (PointInPolygon(poi_outside, path) != PointInPolygonResult::IsOutside) - ++outside_subject_count; - EXPECT_EQ(outside_subject_count, 0); - } - - const std::vector points_of_interest_inside = { - Point64(21887, 10430), - Point64(21843, 10520), - Point64(21810, 10686), - Point64(21900, 10461) - }; - - // confirm that each 'points_of_interest_inside' is inside a subject, - // and inside only one subject (to exclude possible subject holes) - for (const auto& poi_inside : points_of_interest_inside) - { - int inside_subject_count = 0; - for (const auto& path : subject) - { - if (PointInPolygon(poi_inside, path) != PointInPolygonResult::IsOutside) - ++inside_subject_count; - } - EXPECT_EQ(inside_subject_count, 1); - } - - PolyTree64 solution_tree; - Paths64 solution_open; - Clipper64 c; - c.AddSubject(subject); - c.AddOpenSubject(subject_open); - c.AddClip(clip); - c.Execute(ct, FillRule::Negative, solution_tree, solution_open); - - const auto solution_paths = PolyTreeToPaths64(solution_tree); - - ASSERT_FALSE(solution_paths.empty()); - - const double subject_area = -Area(subject); //negate (see fillrule) - const double solution_tree_area = GetPolytreeArea(solution_tree); - const double solution_paths_area = Area(solution_paths); - - // 1a. check solution_paths_area is smaller than subject_area - EXPECT_LT(solution_paths_area, subject_area); - // 1b. but not too much smaller - EXPECT_GT(solution_paths_area, (subject_area * 0.92)); - - // 2. check solution_tree's area matches solution_paths' area - EXPECT_NEAR(solution_tree_area, solution_paths_area, 0.0001); - - // 3. check that all children are inside their parents - EXPECT_TRUE(CheckPolytreeFullyContainsChildren(solution_tree)); - - // 4. confirm all 'point_of_interest_outside' are outside polytree - for (const auto& poi_outside : points_of_interest_outside) - EXPECT_FALSE(PolytreeContainsPoint(solution_tree, poi_outside)); - - // 5. confirm all 'point_of_interest_inside' are inside polytree - for (const auto& poi_inside : points_of_interest_inside) - EXPECT_TRUE(PolytreeContainsPoint(solution_tree, poi_inside)); - -} \ No newline at end of file diff --git a/CPP/Tests/TestPolytreeHoles3.cpp b/CPP/Tests/TestPolytreeHoles3.cpp deleted file mode 100644 index 561d2ce8..00000000 --- a/CPP/Tests/TestPolytreeHoles3.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include "clipper2/clipper.h" - -using namespace Clipper2Lib; - -TEST(Clipper2Tests, TestPolytreeHoles3) -{ - - Paths64 subject, clip, sol; - PolyTree64 solution; - Clipper64 c; - subject.push_back(MakePath({ 1072,501, 1072,501, 1072,539, 1072,539, 1072,539, 870,539, - 870,539, 870,539, 870,520, 894,520, 898,524, 911,524, 915,520, 915,520, 936,520, - 940,524, 953,524, 957,520, 957,520, 978,520, 983,524, 995,524, 1000,520, 1021,520, - 1025,524, 1038,524, 1042,520, 1038,516, 1025,516, 1021,520, 1000,520, 995,516, - 983,516, 978,520, 957,520, 953,516, 940,516, 936,520, 915,520, 911,516, 898,516, - 894,520, 870,520, 870,516, 870,501, 870,501, 870,501, 1072,501 })); - - clip.push_back(MakePath({ 870,501, 971,501, 971,539, 870,539 })); - - c.AddSubject(subject); - c.AddClip(clip); - c.Execute(ClipType::Intersection, FillRule::NonZero, solution); - EXPECT_TRUE(solution.Count() == 1 && solution[0]->Count() == 2); -} \ No newline at end of file diff --git a/CPP/Tests/TestPolytreeHoles4.cpp b/CPP/Tests/TestPolytreeHoles4.cpp deleted file mode 100644 index 290abddd..00000000 --- a/CPP/Tests/TestPolytreeHoles4.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include "clipper2/clipper.h" - -using namespace Clipper2Lib; - -TEST(Clipper2Tests, TestPolytreeHoles4) -{ - Paths64 subject; - PolyTree64 solution; - Clipper64 c; - subject.push_back(MakePath({ 50,500, 50,300, 100,300, 100,350, 150,350, - 150,250, 200,250, 200,450, 350,450, 350,200, 400,200, 400,225, 450,225, - 450,175, 400,175, 400,200, 350,200, 350,175, 200,175, 200,250, 150,250, - 150,200, 100,200, 100,300, 50,300, 50,125, 500,125, 500,500 })); - subject.push_back(MakePath({ 250,425, 250,375, 300,375, 300,425})); - - c.AddSubject(subject); - c.Execute(ClipType::Union, FillRule::NonZero, solution); - // Polytree root - // +- Polygon with 3 holes. - // +- Hole with 1 nested polygon. - // +-Polygon - // +- Hole - // +- Hole - EXPECT_TRUE(solution.Count() == 1 && solution[0]->Count() == 3); -} \ No newline at end of file diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 54864bde..4d58b23b 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 6 August 2023 * +* Date : 27 August 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2708,6 +2708,15 @@ private static bool Path1InsidePath2(OutPt op1, OutPt op2) return InternalClipper.PointInPolygon(mp, path2) != PointInPolygonResult.IsOutside; } + private void MoveSplits(OutRec fromOr, OutRec toOr) + { + if (fromOr.splits == null) return; + toOr.splits ??= new List(); + foreach (int i in fromOr.splits) + toOr.splits.Add(i); + fromOr.splits = null; + } + private void ProcessHorzJoins() { foreach (HorzJoin j in _horzJoinList) @@ -2726,27 +2735,35 @@ private void ProcessHorzJoins() { or2 = NewOutRec(); or2.pts = op1b; - FixOutRecPts(or2); + + //if or1->pts has moved to or2 then update or1->pts!! if (or1.pts!.outrec == or2) { or1.pts = j.op1; or1.pts.outrec = or1; } - if (_using_polytree) //#498, #520, #584, D#576 + if (_using_polytree) //#498, #520, #584, D#576, #618 { if (Path1InsidePath2(or1.pts, or2.pts)) { + //swap or1's & or2's pts + OutPt tmp = or1.pts; + or1.pts = or2.pts; + or2.pts = tmp; + FixOutRecPts(or1); + FixOutRecPts(or2); + //or2 is now inside or1 or2.owner = or1.owner; - SetOwner(or1, or2); } + else if (Path1InsidePath2(or2.pts, or1.pts)) + or2.owner = or1; else - { - SetOwner(or2, or1); - or1.splits ??= new List(); - or1.splits.Add(or2.idx); - } + or2.owner = or1.owner; + + or1.splits ??= new List(); + or1.splits.Add(or2.idx); } else or2.owner = or1; @@ -2755,7 +2772,10 @@ private void ProcessHorzJoins() { or2.pts = null; if (_using_polytree) + { SetOwner(or2, or1); + MoveSplits(or2, or1); //#618 + } else or2.owner = or1; } @@ -3040,7 +3060,7 @@ private bool CheckSplitOwner(OutRec outrec, List? splits) foreach (int i in splits!) { OutRec? split = GetRealOutRec(_outrecList[i]); - if (split == null || split.recursiveSplit == outrec) continue; + 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) && diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index d312731f..e78e7f7c 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 6 August 2023 * +* Date : 27 August 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -3018,11 +3018,22 @@ procedure TClipperBase.ConvertHorzSegsToJoins; end; //------------------------------------------------------------------------------ +procedure MoveSplits(fromOr, toOr: POutRec); +var + i: integer; +begin + if not assigned(fromOr.splits) then Exit; + for i := 0 to High(fromOr.splits) do + AddSplit(toOr, fromOr.splits[i]); + fromOr.splits := nil; +end; +//------------------------------------------------------------------------------ + procedure TClipperBase.ProcessHorzJoins; var i: integer; or1, or2: POutRec; - op1b, op2b: POutPt; + op1b, op2b, tmp: POutPt; begin for i := 0 to FHorzJoinList.Count -1 do with PHorzJoin(FHorzJoinList[i])^ do @@ -3045,32 +3056,45 @@ procedure TClipperBase.ProcessHorzJoins; or2.pts := op1b; FixOutRecPts(or2); + //if or1->pts has moved to or2 then update or1->pts!! if or1.pts.outrec = or2 then begin or1.pts := op1; or1.pts.outrec := or1; end; - if FUsingPolytree then //#498, #520, #584, D#576 + if FUsingPolytree then //#498, #520, #584, D#576, #618 begin if Path1InsidePath2(or1.pts, or2.pts) then begin - or2.owner := or1.owner; - SetOwner(or1, or2); - end else + //swap or1's & or2's pts + tmp := or1.pts; + or1.pts := or2.pts; + or2.pts := tmp; + FixOutRecPts(or1); + FixOutRecPts(or2); + //or2 is now inside or1 + or2.owner := or1; + end + else if Path1InsidePath2(or2.pts, or1.pts) then begin - SetOwner(or2, or1); - AddSplit(or1, or2); - end; + or2.owner := or1; + end + else + or2.owner := or1.owner; + + AddSplit(or1, or2); end else or2.owner := or1; end else begin or2.pts := nil; - if FUsingPolytree then - SetOwner(or2, or1) - else + if FUsingPolytree then + begin + SetOwner(or2, or1); + MoveSplits(or2, or1); //#618 + end else or2.owner := or1; end; end; @@ -3738,9 +3762,11 @@ function TClipperBase.CheckSplitOwner(outrec: POutRec; const splits: TOutRecArra for i := 0 to High(splits) do begin split := GetRealOutRec(splits[i]); - if (split = nil) or (split.recursiveCheck = outrec) then Continue; + if (split = nil) or + (split = outrec) or + (split.recursiveCheck = outrec) then Continue; + split.recursiveCheck := outrec; // prevent infinite loops - if Assigned(split.splits) and CheckSplitOwner(outrec, split.splits) then Exit else if IsValidOwner(outrec, split) and From ba8e8a2b4055ba5aa4cf75d4b21ddf22020d3f51 Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 3 Sep 2023 15:01:05 +1000 Subject: [PATCH 35/95] Fixed another Polytree nesting bug (C# only) //#638 --- CSharp/Clipper2Lib/Clipper.Engine.cs | 39 ++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 4d58b23b..31e6b016 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 27 August 2023 * +* Date : 3 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -13,6 +13,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Reflection.Emit; using System.Runtime.CompilerServices; namespace Clipper2Lib @@ -2755,7 +2756,7 @@ private void ProcessHorzJoins() FixOutRecPts(or1); FixOutRecPts(or2); //or2 is now inside or1 - or2.owner = or1.owner; + or2.owner = or1; } else if (Path1InsidePath2(or2.pts, or1.pts)) or2.owner = or1; @@ -3500,9 +3501,38 @@ public void Clear() { _childs.Clear(); } - } // PolyPathBase class - public class PolyPath64 : PolyPathBase + internal string ToStringInternal(int idx, int level) + { + string result = "", padding = "", plural = "s"; + 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); + else + result += string.Format("{0}+- polygon ({1}) contains {2} hole{3}.\n", padding, idx, _childs.Count, plural); + + for (int i = 0; i < Count; i++) + if (_childs[i].Count > 0) + result += _childs[i].ToStringInternal(i, level +1); + return result; + } + + 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); + for (int i = 0; i < Count; i++) + if (_childs[i].Count > 0) + result += _childs[i].ToStringInternal(i, 1); + return result + '\n'; + } + +} // PolyPathBase class + +public class PolyPath64 : PolyPathBase { public Path64? Polygon { get; private set; } // polytree root's polygon == null @@ -3547,7 +3577,6 @@ public double Area() return result; } } - public class PolyPathD : PolyPathBase { internal double Scale { get; set; } From 13bf2aa7818445241a4e82a4a6325af6ded4745f Mon Sep 17 00:00:00 2001 From: angusj Date: Sat, 9 Sep 2023 07:53:47 +1000 Subject: [PATCH 36/95] Fixed a bug in RectClip (#637) --- CPP/Clipper2Lib/src/clipper.rectclip.cpp | 130 +++++++++------ CPP/Tests/TestPolytreeHoles.cpp | 20 +++ CPP/Tests/TestRectClip.cpp | 13 ++ CSharp/Clipper2Lib/Clipper.RectClip.cs | 161 +++++++++++------- Delphi/Clipper2Lib/Clipper.RectClip.pas | 201 ++++++++++++++--------- 5 files changed, 342 insertions(+), 183 deletions(-) diff --git a/CPP/Clipper2Lib/src/clipper.rectclip.cpp b/CPP/Clipper2Lib/src/clipper.rectclip.cpp index 221cd9bb..9aa0fc0f 100644 --- a/CPP/Clipper2Lib/src/clipper.rectclip.cpp +++ b/CPP/Clipper2Lib/src/clipper.rectclip.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 6 August 2023 * +* Date : 8 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -24,11 +24,11 @@ namespace Clipper2Lib { for (const Point64& pt : path2) { PointInPolygonResult pip = PointInPolygon(pt, path1); - switch (pip) + switch (pip) { case PointInPolygonResult::IsOutside: ++io_count; break; - case PointInPolygonResult::IsInside: --io_count; break; - default: continue; + case PointInPolygonResult::IsInside: --io_count; break; + default: continue; } if (std::abs(io_count) > 1) break; } @@ -66,6 +66,56 @@ namespace Clipper2Lib { return true; } + inline bool IsHorizontal(const Point64& pt1, const Point64& pt2) + { + return pt1.y == pt2.y; + } + + inline bool GetSegmentIntersection(const Point64& p1, + const Point64& p2, const Point64& p3, const Point64& p4, Point64& ip) + { + double res1 = CrossProduct(p1, p3, p4); + double res2 = CrossProduct(p2, p3, p4); + if (res1 == 0) + { + ip = p1; + if (res2 == 0) return false; // segments are collinear + else if (p1 == p3 || p1 == p4) return true; + //else if (p2 == p3 || p2 == p4) { ip = p2; return true; } + else if (IsHorizontal(p3, p4)) return ((p1.x > p3.x) == (p1.x < p4.x)); + else return ((p1.y > p3.y) == (p1.y < p4.y)); + } + else if (res2 == 0) + { + ip = p2; + if (p2 == p3 || p2 == p4) return true; + else if (IsHorizontal(p3, p4)) return ((p2.x > p3.x) == (p2.x < p4.x)); + else return ((p2.y > p3.y) == (p2.y < p4.y)); + } + if ((res1 > 0) == (res2 > 0)) return false; + + double res3 = CrossProduct(p3, p1, p2); + double res4 = CrossProduct(p4, p1, p2); + if (res3 == 0) + { + ip = p3; + if (p3 == p1 || p3 == p2) return true; + else if (IsHorizontal(p1, p2)) return ((p3.x > p1.x) == (p3.x < p2.x)); + else return ((p3.y > p1.y) == (p3.y < p2.y)); + } + else if (res4 == 0) + { + ip = p4; + if (p4 == p1 || p4 == p2) return true; + else if (IsHorizontal(p1, p2)) return ((p4.x > p1.x) == (p4.x < p2.x)); + else return ((p4.y > p1.y) == (p4.y < p2.y)); + } + if ((res3 > 0) == (res4 > 0)) return false; + + // segments must intersect to get here + return GetIntersectPoint(p1, p2, p3, p4, ip); + } + inline bool GetIntersection(const Path64& rectPath, const Point64& p, const Point64& p2, Location& loc, Point64& ip) { @@ -74,100 +124,84 @@ namespace Clipper2Lib { switch (loc) { case Location::Left: - if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) - GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); - else if (p.y < rectPath[0].y && - SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) return true; + else if ((p.y < rectPath[0].y) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) { - GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); loc = Location::Top; + return true; } - else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) { - GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); loc = Location::Bottom; + return true; } else return false; - break; case Location::Top: - if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) - GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); - else if (p.x < rectPath[0].x && - SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) return true; + else if ((p.x < rectPath[0].x) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) { - GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); loc = Location::Left; + return true; } - else if (p.x > rectPath[1].x && - SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) { - GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); loc = Location::Right; + return true; } else return false; - break; case Location::Right: - if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) - GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); - else if (p.y < rectPath[0].y && - SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) return true; + else if ((p.y < rectPath[1].y) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) { - GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); loc = Location::Top; + return true; } - else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) { - GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); loc = Location::Bottom; + return true; } else return false; - break; case Location::Bottom: - if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) - GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); - else if (p.x < rectPath[3].x && - SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) return true; + else if ((p.x < rectPath[3].x) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) { - GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); loc = Location::Left; + return true; } - else if (p.x > rectPath[2].x && - SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) { - GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); loc = Location::Right; + return true; } else return false; - break; default: // loc == rInside - if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) { - GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); loc = Location::Left; + return true; } - else if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + else if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) { - GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); loc = Location::Top; + return true; } - else if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) { - GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); loc = Location::Right; + return true; } - else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) { - GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); loc = Location::Bottom; + return true; } else return false; - break; } - return true; } inline Location GetAdjacentLocation(Location loc, bool isClockwise) diff --git a/CPP/Tests/TestPolytreeHoles.cpp b/CPP/Tests/TestPolytreeHoles.cpp index ed7a0a57..2ad65551 100644 --- a/CPP/Tests/TestPolytreeHoles.cpp +++ b/CPP/Tests/TestPolytreeHoles.cpp @@ -247,7 +247,27 @@ TEST(Clipper2Tests, TestPolytreeHoles6) //#618 // + -Polygon (2) contains 1 holes. EXPECT_TRUE(tree.Count() == 3 && tree[2]->Count() == 1); +} + + +TEST(Clipper2Tests, TestPolytreeHoles7) //#618 +{ + Paths64 subject; + subject.push_back(MakePath({ 0, 0, 100000, 0, 100000, 100000, 200000, 100000, + 200000, 0, 300000, 0, 300000, 200000, 0, 200000 })); + subject.push_back(MakePath({ 0, 0, 0, -100000, 250000, -100000, 250000, 0 })); + + PolyTree64 polytree; + Clipper64 c; + c.AddSubject(subject); + + c.Execute(ClipType::Union, FillRule::NonZero, polytree); + //std::cout << polytree << std::endl; + EXPECT_TRUE(polytree.Count() == 1 && polytree[0]->Count() == 1); } + + + diff --git a/CPP/Tests/TestRectClip.cpp b/CPP/Tests/TestRectClip.cpp index 949ea979..2b56d16b 100644 --- a/CPP/Tests/TestRectClip.cpp +++ b/CPP/Tests/TestRectClip.cpp @@ -66,4 +66,17 @@ TEST(Clipper2Tests, TestRectClip2) //#597 //std::cout << solution << std::endl; EXPECT_TRUE(solution.size() == 1 && solution[0].size() == 4); +} + +TEST(Clipper2Tests, TestRectClip3) //#637 +{ + Rect64 r(-1800000000LL, -137573171LL, -1741475021LL, 3355443LL); + Paths64 subject, clip, solution; + subject.push_back(MakePath({ -1800000000LL, 10005000LL, + -1800000000LL, -5000LL, -1789994999LL, -5000LL, -1789994999LL, 10005000LL })); + clip.push_back(r.AsPath()); + + solution = RectClip(r, subject); + //std::cout << solution << std::endl; + EXPECT_TRUE(solution.size() == 1); } \ No newline at end of file diff --git a/CSharp/Clipper2Lib/Clipper.RectClip.cs b/CSharp/Clipper2Lib/Clipper.RectClip.cs index a570c67d..ea4f8623 100644 --- a/CSharp/Clipper2Lib/Clipper.RectClip.cs +++ b/CSharp/Clipper2Lib/Clipper.RectClip.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 6 August 2023 * +* Date : 8 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -275,6 +275,67 @@ static protected bool GetLocation(Rect64 rec, Point64 pt, out Location loc) return true; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsHorizontal(Point64 pt1, Point64 pt2) + { + return pt1.Y == pt2.Y; + } + + private static bool GetSegmentIntersection(Point64 p1, + Point64 p2, Point64 p3, Point64 p4, out Point64 ip) + { + double res1 = InternalClipper.CrossProduct(p1, p3, p4); + double res2 = InternalClipper.CrossProduct(p2, p3, p4); + if (res1 == 0) + { + ip = p1; + if (res2 == 0) return false; // segments are collinear + else if (p1 == p3 || p1 == p4) return true; + //else if (p2 == p3 || p2 == p4) { ip = p2; return true; } + else if (IsHorizontal(p3, p4)) return ((p1.X > p3.X) == (p1.X < p4.X)); + else return ((p1.Y > p3.Y) == (p1.Y < p4.Y)); + } + else if (res2 == 0) + { + ip = p2; + if (p2 == p3 || p2 == p4) return true; + else if (IsHorizontal(p3, p4)) return ((p2.X > p3.X) == (p2.X < p4.X)); + else return ((p2.Y > p3.Y) == (p2.Y < p4.Y)); + } + + if ((res1 > 0) == (res2 > 0)) + { + ip = new Point64(0, 0); + return false; + } + + double res3 = InternalClipper.CrossProduct(p3, p1, p2); + double res4 = InternalClipper.CrossProduct(p4, p1, p2); + if (res3 == 0) + { + ip = p3; + if (p3 == p1 || p3 == p2) return true; + else if (IsHorizontal(p1, p2)) return ((p3.X > p1.X) == (p3.X < p2.X)); + else return ((p3.Y > p1.Y) == (p3.Y < p2.Y)); + } + else if (res4 == 0) + { + ip = p4; + if (p4 == p1 || p4 == p2) return true; + else if (IsHorizontal(p1, p2)) return ((p4.X > p1.X) == (p4.X < p2.X)); + else return ((p4.Y > p1.Y) == (p4.Y < p2.Y)); + } + if ((res3 > 0) == (res4 > 0)) + { + ip = new Point64(0, 0); + return false; + } + + // segments must intersect to get here + return InternalClipper.GetIntersectPoint(p1, p2, p3, p4, out ip); + } + + static protected bool GetIntersection(Path64 rectPath, Point64 p, Point64 p2, ref Location loc, out Point64 ip) { // gets the pt of intersection between rectPath and segment(p, p2) that's closest to 'p' @@ -283,108 +344,88 @@ static protected bool GetIntersection(Path64 rectPath, Point64 p, Point64 p2, re switch (loc) { case Location.left: - if (InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[3], true)) - { - InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[3], out ip); - } - else if (p.Y < rectPath[0].Y && - InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[1], true)) - { - InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[1], out ip); + 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)) + { loc = Location.top; + return true; } - else if (InternalClipper.SegsIntersect(p, p2, rectPath[2], rectPath[3], true)) - { - InternalClipper.GetIntersectPt(p, p2, rectPath[2], rectPath[3], out ip); + else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) + { loc = Location.bottom; + return true; } else return false; - break; case Location.right: - if (InternalClipper.SegsIntersect(p, p2, rectPath[1], rectPath[2], true)) - { - InternalClipper.GetIntersectPt(p, p2, rectPath[1], rectPath[2], out ip); - } - else if (p.Y < rectPath[0].Y && - InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[1], true)) - { - InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[1], out ip); + 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)) + { loc = Location.top; + return true; } - else if (InternalClipper.SegsIntersect(p, p2, rectPath[2], rectPath[3], true)) - { - InternalClipper.GetIntersectPt(p, p2, rectPath[2], rectPath[3], out ip); + else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) + { loc = Location.bottom; + return true; } else return false; - break; case Location.top: - if (InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[1], true)) - { - InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[1], out ip); - } - else if (p.X < rectPath[0].X && - InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[3], true)) - { - InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[3], out ip); + 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)) + { loc = Location.left; + return true; } - else if (p.X > rectPath[1].X && - InternalClipper.SegsIntersect(p, p2, rectPath[1], rectPath[2], true)) - { - InternalClipper.GetIntersectPt(p, p2, rectPath[1], rectPath[2], out ip); + else if (p.X > rectPath[1].X && GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) + { loc = Location.right; + return true; } else return false; - break; case Location.bottom: - if (InternalClipper.SegsIntersect(p, p2, rectPath[2], rectPath[3], true)) - { - InternalClipper.GetIntersectPt(p, p2, rectPath[2], rectPath[3], out ip); - } - else if (p.X < rectPath[3].X && - InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[3], true)) + 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)) { - InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[3], out ip); loc = Location.left; + return true; } - else if (p.X > rectPath[2].X && - InternalClipper.SegsIntersect(p, p2, rectPath[1], rectPath[2], true)) + else if (p.X > rectPath[2].X && GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) { - InternalClipper.GetIntersectPt(p, p2, rectPath[1], rectPath[2], out ip); loc = Location.right; + return true; } else return false; - break; - case Location.inside: - if (InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[3], true)) + default: + if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip)) { - InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[3], out ip); loc = Location.left; + return true; } - else if (InternalClipper.SegsIntersect(p, p2, rectPath[0], rectPath[1], true)) + else if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip)) { - InternalClipper.GetIntersectPt(p, p2, rectPath[0], rectPath[1], out ip); loc = Location.top; + return true; } - else if (InternalClipper.SegsIntersect(p, p2, rectPath[1], rectPath[2], true)) + else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) { - InternalClipper.GetIntersectPt(p, p2, rectPath[1], rectPath[2], out ip); loc = Location.right; + return true; } - else if (InternalClipper.SegsIntersect(p, p2, rectPath[2], rectPath[3], true)) + else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) { - InternalClipper.GetIntersectPt(p, p2, rectPath[2], rectPath[3], out ip); loc = Location.bottom; + return true; } else return false; - break; } - return true; } protected void GetNextLocation(Path64 path, diff --git a/Delphi/Clipper2Lib/Clipper.RectClip.pas b/Delphi/Clipper2Lib/Clipper.RectClip.pas index 29b3c9bb..c687a1fc 100644 --- a/Delphi/Clipper2Lib/Clipper.RectClip.pas +++ b/Delphi/Clipper2Lib/Clipper.RectClip.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 6 August 2023 * +* Date : 9 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -114,101 +114,152 @@ function GetLocation(const rec: TRect64; const pt: TPoint64; end; //------------------------------------------------------------------------------ +function IsHorizontal(pt1: TPoint64; pt2: TPoint64): Boolean; + {$IFDEF INLINING} inline; {$ENDIF} +begin + Result := pt1.Y = pt2.Y; +end; +//------------------------------------------------------------------------------ + +function GetSegmentIntersection(p1: TPoint64; +p2: TPoint64; p3: TPoint64; p4: TPoint64; out ip: TPoint64): Boolean; +var + res1, res2, res3, res4: double; +begin + res1 := CrossProduct(p1, p3, p4); + res2 := CrossProduct(p2, p3, p4); + if (res1 = 0) then + begin + ip := p1; + if (res2 = 0) then + result := false // segments are collinear + else if PointsEqual(p1, p3) or PointsEqual(p1, p4) then + result := true + else if (IsHorizontal(p3, p4)) then + result := ((p1.X > p3.X) = (p1.X < p4.X)) + else + result := (p1.Y > p3.Y) = (p1.Y < p4.Y); + Exit; + end; + + if (res2 = 0) then + begin + ip := p2; + if PointsEqual(p2, p3) or PointsEqual(p2, p4) then + Result := true + else if (IsHorizontal(p3, p4)) then + Result := ((p2.X > p3.X) = (p2.X < p4.X)) + else Result := ((p2.Y > p3.Y) = (p2.Y < p4.Y)); + Exit; + end; + + if ((res1 > 0) = (res2 > 0)) then + begin + //ip := Point64(0, 0); + Result := false; + Exit; + end; + + res3 := CrossProduct(p3, p1, p2); + res4 := CrossProduct(p4, p1, p2); + if (res3 = 0) then + begin + ip := p3; + if PointsEqual(p3, p1) or PointsEqual(p3, p2) then + Result := true + else if (IsHorizontal(p1, p2)) then + Result := (p3.X > p1.X) = (p3.X < p2.X) + else + Result := (p3.Y > p1.Y) = (p3.Y < p2.Y); + end + else if (res4 = 0) then + begin + ip := p4; + if PointsEqual(p4, p1) or PointsEqual(p4, p2) then + Result := true + else if (IsHorizontal(p1, p2)) then + Result := (p4.X > p1.X) = (p4.X < p2.X) + else + Result := (p4.Y > p1.Y) = (p4.Y < p2.Y); + end + else if ((res3 > 0) = (res4 > 0)) then + begin + //ip := Point64(0, 0); + Result := false; + end + else + // segments must intersect to get here + Result := GetIntersectPoint(p1, p2, p3, p4, ip); +end; +//------------------------------------------------------------------------------ + function GetIntersection(const rectPath: TPath64; const p, p2: TPoint64; var loc: TLocation; out ip: TPoint64): Boolean; begin // gets the intersection closest to 'p' // when Result = false, loc will remain unchanged - Result := false; + Result := True; case loc of locLeft: - if SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true) then - GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip) + if GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip) then + //Result := True else if (p.Y < rectPath[0].Y) and - SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true) then - begin - GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); - loc := locTop; - end - else if SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true) then - begin - GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); - loc := locBottom; - end - else Exit; + GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip) then + loc := locTop + else if GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip) then + loc := locBottom + else + Result := False; + locRight: - if SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true) then - GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip) + if GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip) then + //Result := True else if (p.Y < rectPath[0].Y) and - SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true) then - begin - GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); - loc := locTop; - end - else if SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true) then - begin - GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); - loc := locBottom; - end - else Exit; + GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip) then + loc := locTop + else if GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip) then + loc := locBottom + else + Result := False; + locTop: - if SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true) then - GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip) + if GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip) then + //Result := True else if (p.X < rectPath[0].X) and - SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true) then - begin - GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); - loc := locLeft; - end + GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip) then + loc := locLeft else if (p.X > rectPath[1].X) and - SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true) then - begin - GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); - loc := locRight; - end - else Exit; + GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip) then + loc := locRight + else + Result := False; + locBottom: - if SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true) then - GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip) + if GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip) then + //Result := True else if (p.X < rectPath[3].X) and - SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true) then - begin - GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); - loc := locLeft; - end + GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip) then + loc := locLeft else if (p.X > rectPath[2].X) and - SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true) then - begin - GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); - loc := locRight; - end - else Exit; + GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip) then + loc := locRight + else + Result := False; else // loc = rInside begin - if SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true) then - begin - GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); - loc := locLeft; - end else if SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true) then - begin - GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); - loc := locTop; - end - else if SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true) then - begin - GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); - loc := locRight; - end - else if SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true) then - begin - GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); - loc := locBottom; - end - else Exit; + if GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip) then + loc := locLeft + else if GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip) then + loc := locTop + else if GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip) then + loc := locRight + else if GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip) then + loc := locBottom + else + Result := False; end; end; - Result := true; end; //------------------------------------------------------------------------------ From e6e22018666de4f423bb98550a120aa7c14ba820 Mon Sep 17 00:00:00 2001 From: angusj Date: Sat, 9 Sep 2023 19:02:32 +1000 Subject: [PATCH 37/95] Fixed a bug when offsetting a single point path (#617) --- CPP/Clipper2Lib/src/clipper.offset.cpp | 5 +++-- CSharp/Clipper2Lib/Clipper.Offset.cs | 5 +++-- Delphi/Clipper2Lib/Clipper.Offset.pas | 7 ++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index 5faa2617..c369993d 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 7 August 2023 * +* Date : 9 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -539,7 +539,8 @@ void ClipperOffset::DoGroupOffset(Group& group) if (group.join_type == JoinType::Round) { double radius = abs_delta; - group.path = Ellipse(path[0], radius, radius); + int steps = static_cast(std::ceil(steps_per_rad_ * 2 * PI)); //#617 + group.path = Ellipse(path[0], radius, radius, steps); #ifdef USINGZ for (auto& p : group.path) p.z = path[0].z; #endif diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index d90c95ea..d5984e22 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 7 August 2023 * +* Date : 9 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -664,7 +664,8 @@ private void DoGroupOffset(Group group) if (group.endType == EndType.Round) { double r = absDelta; - group.outPath = Clipper.Ellipse(path[0], r, r); + int steps = (int)Math.Ceiling(_stepsPerRad * 2 * Math.PI); + group.outPath = Clipper.Ellipse(path[0], r, r, steps); #if USINGZ group.outPath = InternalClipper.SetZ(group.outPath, path[0].Z); #endif diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index b02b936b..1214fa58 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 7 August 2023 * +* Date : 9 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -298,7 +298,7 @@ function GetPerpendicD(const pt: TPoint64; const norm: TPointD; delta: double): procedure TClipperOffset.DoGroupOffset(group: TGroup); var - i,j, len, lowestIdx: Integer; + i,j, len, lowestIdx, steps: Integer; r, stepsPer360, arcTol, area: Double; absDelta: double; rec: TRect64; @@ -369,7 +369,8 @@ procedure TClipperOffset.DoGroupOffset(group: TGroup); r := absDelta; with fInPath[0] do begin - fOutPath := Path64(Ellipse(RectD(X-r, Y-r, X+r, Y+r))); + steps := Ceil(fStepsPerRad * TwoPi); //#617 + fOutPath := Path64(Ellipse(RectD(X-r, Y-r, X+r, Y+r), steps)); {$IFDEF USINGZ} for j := 0 to high(fOutPath) do fOutPath[j].Z := Z; From 388041f1150b8dbe67bf87d7300ae7d4ac291f78 Mon Sep 17 00:00:00 2001 From: angusj Date: Sat, 16 Sep 2023 20:31:13 +1000 Subject: [PATCH 38/95] Fixed a minor bug in ClipperOffset (#593) --- CPP/Clipper2Lib/src/clipper.offset.cpp | 12 +- CPP/Tests/TestOffsets.cpp | 308 ++++++++++++++++++++----- CSharp/Clipper2Lib/Clipper.Offset.cs | 8 +- Delphi/Clipper2Lib/Clipper.Offset.pas | 10 +- 4 files changed, 264 insertions(+), 74 deletions(-) diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index c369993d..496ad9a7 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 9 September 2023 * +* Date : 16 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -327,11 +327,7 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) return; } - if (cos_a > 0.999) // almost straight - less than 2.5 degree (#424, #526) - { - DoMiter(group, path, j, k, cos_a); - } - else if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) + if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) // test for concavity first (#593) { // is concave group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_)); @@ -340,6 +336,10 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) group.path.push_back(path[j]); // (#405) group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); } + else if (cos_a > 0.999) // almost straight - less than 2.5 degree (#424, #526) + { + DoMiter(group, path, j, k, cos_a); + } else if (join_type_ == JoinType::Miter) { // miter unless the angle is so acute the miter would exceeds ML diff --git a/CPP/Tests/TestOffsets.cpp b/CPP/Tests/TestOffsets.cpp index 984de03c..dd713364 100644 --- a/CPP/Tests/TestOffsets.cpp +++ b/CPP/Tests/TestOffsets.cpp @@ -88,68 +88,28 @@ TEST(Clipper2Tests, TestOffsets2) { // see #448 & #456 TEST(Clipper2Tests, TestOffsets3) // see #424 { - Paths64 subjects = {{ - {1525311078, 1352369439}, - {1526632284, 1366692987}, - {1519397110, 1367437476}, - {1520246456, 1380177674}, - {1520613458, 1385913385}, - {1517383844, 1386238444}, - {1517771817, 1392099983}, - {1518233190, 1398758441}, - {1518421934, 1401883197}, - {1518694564, 1406612275}, - {1520267428, 1430289121}, - {1520770744, 1438027612}, - {1521148232, 1443438264}, - {1521441833, 1448964260}, - {1521683005, 1452518932}, - {1521819320, 1454374912}, - {1527943004, 1454154711}, - {1527649403, 1448523858}, - {1535901696, 1447989084}, - {1535524209, 1442788147}, - {1538953052, 1442463089}, - {1541553521, 1442242888}, - {1541459149, 1438855987}, - {1538764308, 1439076188}, - {1538575565, 1436832236}, - {1538764308, 1436832236}, - {1536509870, 1405374956}, - {1550497874, 1404347351}, - {1550214758, 1402428457}, - {1543818445, 1402868859}, - {1543734559, 1402124370}, - {1540672717, 1402344571}, - {1540473487, 1399995761}, - {1524996506, 1400981422}, - {1524807762, 1398223667}, - {1530092585, 1397898609}, - {1531675935, 1397783265}, - {1531392819, 1394920653}, - {1529809469, 1395025510}, - {1529348096, 1388880855}, - {1531099218, 1388660654}, - {1530826588, 1385158410}, - {1532955197, 1384938209}, - {1532661596, 1379003269}, - {1532472852, 1376235028}, - {1531277476, 1376350372}, - {1530050642, 1361806623}, - {1599487345, 1352704983}, - {1602758902, 1378489467}, - {1618990858, 1376350372}, - {1615058698, 1344085688}, - {1603230761, 1345700495}, - {1598648484, 1346329641}, - {1598931599, 1348667965}, - {1596698132, 1348993024}, - {1595775386, 1342722540} }}; - - Paths64 solution = InflatePaths(subjects, -209715, - JoinType::Miter, EndType::Polygon); + {1525311078, 1352369439}, {1526632284, 1366692987}, {1519397110, 1367437476}, + {1520246456, 1380177674}, {1520613458, 1385913385}, {1517383844, 1386238444}, + {1517771817, 1392099983}, {1518233190, 1398758441}, {1518421934, 1401883197}, + {1518694564, 1406612275}, {1520267428, 1430289121}, {1520770744, 1438027612}, + {1521148232, 1443438264}, {1521441833, 1448964260}, {1521683005, 1452518932}, + {1521819320, 1454374912}, {1527943004, 1454154711}, {1527649403, 1448523858}, + {1535901696, 1447989084}, {1535524209, 1442788147}, {1538953052, 1442463089}, + {1541553521, 1442242888}, {1541459149, 1438855987}, {1538764308, 1439076188}, + {1538575565, 1436832236}, {1538764308, 1436832236}, {1536509870, 1405374956}, + {1550497874, 1404347351}, {1550214758, 1402428457}, {1543818445, 1402868859}, + {1543734559, 1402124370}, {1540672717, 1402344571}, {1540473487, 1399995761}, + {1524996506, 1400981422}, {1524807762, 1398223667}, {1530092585, 1397898609}, + {1531675935, 1397783265}, {1531392819, 1394920653}, {1529809469, 1395025510}, + {1529348096, 1388880855}, {1531099218, 1388660654}, {1530826588, 1385158410}, + {1532955197, 1384938209}, {1532661596, 1379003269}, {1532472852, 1376235028}, + {1531277476, 1376350372}, {1530050642, 1361806623}, {1599487345, 1352704983}, + {1602758902, 1378489467}, {1618990858, 1376350372}, {1615058698, 1344085688}, + {1603230761, 1345700495}, {1598648484, 1346329641}, {1598931599, 1348667965}, + {1596698132, 1348993024}, {1595775386, 1342722540} }}; + Paths64 solution = InflatePaths(subjects, -209715, JoinType::Miter, EndType::Polygon); EXPECT_LE(solution[0].size() - subjects[0].size(), 1); } @@ -187,3 +147,231 @@ TEST(Clipper2Tests, TestOffsets4) // see #482 EXPECT_GT(solution[0].size(), 6); } + +TEST(Clipper2Tests, TestOffsets5) // see #593 +{ + Paths64 subject = { + {{5243,14829}, {5243,27114}, {6095,27443}, {6926,27822}, {7734,28247}, {8515,28719}, + {9269,29236}, {9990,29795}, {10679,30395}, {11331,31034}, {11945,31710}, {12518,32421}, + {13050,33163}, {13537,33935}, {13979,34734}, {14374,35558}, {14721,36403}, {15017,37266}, + {15263,38146}, {15458,39038}, {15601,39940}, {15691,40848}, {15728,41761}, {15712,42674}, + {15643,43584}, {15521,44489}, {15347,45386}, {15122,46271}, {14845,47141}, {14519,47994}, + {14143,48826}, {13720,49635}, {13250,50418}, {12736,51173}, {12179,51897}, {11581,52587}, + {10944,53241}, {10270,53857}, {9561,54433}, {8820,54966}, {8050,55456}, {7252,55901}, + {6430,56298}, {5586,56647}, {5243,56773}, {5243,69058}, {6095,69387}, {6926,69765}, + {7734,70191}, {8515,70663}, {9269,71179}, {9990,71738}, {10679,72339}, {11331,72978}, + {11945,73654}, {12518,74364}, {13050,75107}, {13537,75879}, {13979,76678}, {14374,77501}, + {14721,78346}, {15017,79210}, {15263,80089}, {15458,80981}, {15601,81883}, {15691,82792}, + {15728,83704}, {15712,84617}, {15643,85528}, {15521,86433}, {15347,87329}, {15122,88214}, + {14845,89084}, {14519,89937}, {14143,90769}, {13720,91579}, {13250,92362}, {12736,93116}, + {12179,93840}, {11581,94530}, {10944,95184}, {10270,95800}, {9561,96376}, {8820,96910}, + {8050,97400}, {7252,97844}, {6430,98241}, {5586,98590}, {5243,98716}, {5243,111001}, + {6095,111330}, {6926,111708}, {7734,112134}, {8515,112606}, {9269,113123}, {9990,113682}, + {10679,114282}, {11331,114921}, {11945,115597}, {12518,116308}, {13050,117050}, {13537,117822}, + {13979,118621}, {14374,119445}, {14721,120290}, {15017,121153}, {15263,122033}, {15458,122925}, + {15601,123827}, {15691,124735}, {15728,125648}, {15712,126561}, {15643,127471}, {15521,128376}, + {15347,129273}, {15122,130158}, {14845,131028}, {14519,131881}, {14143,132713}, {13720,133522}, + {13250,134305}, {12736,135060}, {12179,135783}, {11581,136473}, {10944,137128}, {10270,137744}, + {9561,138320}, {8820,138853}, {8050,139343}, {7252,139788}, {6430,140185}, {5586,140534}, + {5243,140660}, {5243,152945}, {6095,153274}, {6926,153652}, {7734,154078}, {8515,154550}, + {9269,155066}, {9990,155625}, {10679,156225}, {11331,156865}, {11945,157541}, {12518,158251}, + {13050,158993}, {13537,159766}, {13979,160565}, {14374,161388}, {14721,162233}, {15017,163097}, + {15263,163976}, {15458,164868}, {15601,165770}, {15691,166679}, {15728,167591}, {15712,168504}, + {15643,169415}, {15521,170320}, {15347,171216}, {15122,172101}, {14845,172971}, {14519,173824}, + {14143,174656}, {13720,175465}, {13250,176249}, {12736,177003}, {12179,177727}, {11581,178417}, + {10944,179071}, {10270,179687}, {9561,180263}, {8820,180797}, {8050,181287}, {7252,181731}, + {6430,182128}, {5586,182477}, {5243,182603}, {5243,194888}, {6095,195217}, {6926,195595}, + {7734,196021}, {8515,196493}, {9269,197009}, {9990,197569}, {10679,198169}, {11331,198808}, + {11945,199484}, {12518,200194}, {13050,200937}, {13537,201709}, {13979,202508}, {14374,203332}, + {14721,204176}, {15017,205040}, {15263,205919}, {15458,206812}, {15601,207714}, {15691,208622}, + {15728,209535}, {15712,210448}, {15643,211358}, {15521,212263}, {15347,213160}, {15122,214044}, + {14845,214915}, {14519,215767}, {14143,216600}, {13720,217409}, {13250,218192}, {12736,218947}, + {12179,219670}, {11581,220360}, {10944,221014}, {10270,221631}, {9561,222206}, {8820,222740}, + {8050,223230}, {7252,223675}, {6430,224072}, {5586,224421}, {5243,224547}, {5243,236831}, + {6095,237161}, {6926,237539}, {7734,237965}, {8515,238436}, {9269,238953}, {9990,239512}, + {10679,240112}, {11331,240752}, {11945,241427}, {12518,242138}, {13050,242880}, {13537,243653}, + {13979,244452}, {14374,245275}, {14721,246120}, {15017,246984}, {15263,247863}, {15458,248755}, + {15601,249657}, {15691,250566}, {15728,251478}, {15712,252391}, {15643,253302}, {15521,254207}, + {15347,255103}, {15122,255988}, {14845,256858}, {14519,257711}, {14143,258543}, {13720,259352}, + {13250,260136}, {12736,260890}, {12179,261614}, {11581,262304}, {10944,262958}, {10270,263574}, + {9561,264150}, {8820,264684}, {8050,265174}, {7252,265618}, {6430,266015}, {5586,266364}, + {5243,266490}, {5243,278775}, {6095,279104}, {6926,279482}, {7734,279908}, {8515,280380}, + {9269,280896}, {9990,281456}, {10679,282056}, {11331,282695}, {11945,283371}, {12518,284081}, + {13050,284824}, {13537,285596}, {13979,286395}, {14374,287218}, {14721,288063}, {15017,288927}, + {15263,289806}, {15458,290698}, {15601,291600}, {15691,292509}, {15728,293422}, {15712,294335}, + {15643,295245}, {15521,296150}, {15347,297047}, {15122,297931}, {14845,298802}, {14519,299654}, + {14143,300487}, {13720,301296}, {13250,302079}, {12736,302834}, {12179,303557}, {11581,304247}, + {10944,304901}, {10270,305517}, {9561,306093}, {8820,306627}, {8050,307117}, {7252,307561}, + {6430,307959}, {5586,308308}, {5243,308433}, {5243,320718}, {6092,321046}, {6920,321423}, + {7726,321847}, {8505,322317}, {9256,322831}, {9976,323387}, {10662,323984}, {11313,324620}, + {11926,325293}, {12499,326000}, {13031,326739}, {13518,327507}, {13961,328302}, {14356,329122}, + {14704,329963}, {14829,330305}, {31309,330305}, {31638,329453}, {32016,328622}, {32442,327814}, + {32914,327032}, {33430,326279}, {33989,325557}, {34589,324869}, {35229,324217}, {35905,323603}, + {36615,323029}, {37357,322498}, {38130,322010}, {38929,321568}, {39752,321173}, {40597,320827}, + {41461,320530}, {42340,320284}, {43232,320090}, {44134,319947}, {45043,319857}, {45955,319820}, + {46868,319836}, {47779,319905}, {48684,320026}, {49580,320200}, {50465,320426}, {51335,320702}, + {52188,321029}, {53020,321405}, {53829,321828}, {54613,322297}, {55367,322811}, {56091,323369}, + {56781,323967}, {57435,324604}, {58051,325278}, {58627,325986}, {59161,326727}, {59651,327498}, + {60095,328296}, {60492,329118}, {60841,329962}, {60967,330305}, {77446,330305}, {77776,329453}, + {78154,328622}, {78579,327814}, {79051,327032}, {79568,326279}, {80127,325557}, {80727,324869}, + {81366,324217}, {82042,323603}, {82753,323029}, {83495,322498}, {84267,322010}, {85066,321568}, + {85890,321173}, {86735,320827}, {87598,320530}, {88478,320284}, {89370,320090}, {90272,319947}, + {91181,319857}, {92093,319820}, {93006,319836}, {93917,319905}, {94822,320026}, {95718,320200}, + {96603,320426}, {97473,320702}, {98326,321029}, {99158,321405}, {99967,321828}, {100750,322297}, + {101505,322811}, {102229,323369}, {102919,323967}, {103573,324604}, {104189,325278}, {104765,325986}, + {105299,326727}, {105788,327498}, {106233,328296}, {106630,329118}, {106979,329962}, {107105,330305}, + {123584,330305}, {123913,329453}, {124292,328622}, {124717,327814}, {125189,327032}, {125706,326279}, + {126265,325557}, {126865,324869}, {127504,324217}, {128180,323603}, {128891,323029}, {129633,322498}, + {130405,322010}, {131204,321568}, {132028,321173}, {132873,320827}, {133736,320530}, {134616,320284}, + {135508,320090}, {136410,319947}, {137318,319857}, {138231,319820}, {139144,319836}, {140054,319905}, + {140959,320026}, {141856,320200}, {142741,320426}, {143611,320702}, {144464,321029}, {145296,321405}, + {146105,321828}, {146888,322297}, {147643,322811}, {148366,323369}, {149056,323967}, {149711,324604}, + {150327,325278}, {150903,325986}, {151436,326727}, {151926,327498}, {152371,328296}, {152768,329118}, + {153117,329962}, {153243,330305}, {169722,330305}, {170051,329453}, {170429,328622}, {170855,327814}, + {171327,327032}, {171843,326279}, {172403,325557}, {173003,324869}, {173642,324217}, {174318,323603}, + {175028,323029}, {175771,322498}, {176543,322010}, {177342,321568}, {178165,321173}, {179010,320827}, + {179874,320530}, {180753,320284}, {181646,320090}, {182547,319947}, {183456,319857}, {184369,319820}, + {185282,319836}, {186192,319905}, {187097,320026}, {187994,320200}, {188878,320426}, {189749,320702}, + {190601,321029}, {191434,321405}, {192243,321828}, {193026,322297}, {193781,322811}, {194504,323369}, + {195194,323967}, {195848,324604}, {196464,325278}, {197040,325986}, {197574,326727}, {198064,327498}, + {198508,328296}, {198906,329118}, {199255,329962}, {199380,330305}, {215860,330305}, {216189,329453}, + {216567,328622}, {216993,327814}, {217465,327032}, {217981,326279}, {218540,325557}, {219141,324869}, + {219780,324217}, {220456,323603}, {221166,323029}, {221909,322498}, {222681,322010}, {223480,321568}, + {224303,321173}, {225148,320827}, {226012,320530}, {226891,320284}, {227783,320090}, {228685,319947}, + {229594,319857}, {230506,319820}, {231419,319836}, {232330,319905}, {233235,320026}, {234131,320200}, + {235016,320426}, {235886,320702}, {236739,321029}, {237572,321405}, {238381,321828}, {239164,322297}, + {239919,322811}, {240642,323369}, {241332,323967}, {241986,324604}, {242602,325278}, {243178,325986}, + {243712,326727}, {244202,327498}, {244646,328296}, {245044,329118}, {245393,329962}, {245518,330305}, + {261998,330305}, {262327,329453}, {262705,328622}, {263131,327814}, {263603,327032}, {264119,326279}, + {264678,325557}, {265278,324869}, {265918,324217}, {266594,323603}, {267304,323029}, {268046,322498}, + {268819,322010}, {269618,321568}, {270441,321173}, {271286,320827}, {272150,320530}, {273029,320284}, + {273921,320090}, {274823,319947}, {275732,319857}, {276644,319820}, {277557,319836}, {278468,319905}, + {279373,320026}, {280269,320200}, {281154,320426}, {282024,320702}, {282877,321029}, {283709,321405}, + {284518,321828}, {285302,322297}, {286056,322811}, {286780,323369}, {287470,323967}, {288124,324604}, + {288740,325278}, {289316,325986}, {289850,326727}, {290340,327498}, {290784,328296}, {291181,329118}, + {291530,329962}, {291656,330305}, {308135,330305}, {308465,329453}, {308843,328622}, {309268,327814}, + {309740,327032}, {310257,326279}, {310816,325557}, {311416,324869}, {312055,324217}, {312731,323603}, + {313442,323029}, {314184,322498}, {314956,322010}, {315755,321568}, {316579,321173}, {317424,320827}, + {318287,320530}, {319167,320284}, {320059,320090}, {320961,319947}, {321870,319857}, {322782,319820}, + {323695,319836}, {324606,319905}, {325511,320026}, {326407,320200}, {327292,320426}, {328162,320702}, + {329015,321029}, {329847,321405}, {330656,321828}, {331440,322297}, {332194,322811}, {332918,323369}, + {333608,323967}, {334262,324604}, {334878,325278}, {335454,325986}, {335988,326727}, {336477,327498}, + {336922,328296}, {337319,329118}, {337668,329962}, {337794,330305}, {354273,330305}, {354601,329456}, + {354978,328627}, {355402,327822}, {355871,327043}, {356385,326292}, {356942,325572}, {357539,324885}, + {358175,324234}, {358848,323621}, {359555,323048}, {360294,322517}, {361062,322029}, {361857,321587}, + {362677,321191}, {363518,320844}, {363859,320718}, {363859,308433}, {363008,308104}, {362177,307726}, + {361369,307300}, {360587,306828}, {359834,306312}, {359112,305753}, {358424,305153}, {357772,304513}, + {357158,303837}, {356584,303127}, {356052,302385}, {355565,301612}, {355123,300813}, {354728,299990}, + {354382,299145}, {354085,298281}, {353839,297402}, {353644,296510}, {353502,295608}, {353412,294699}, + {353375,293787}, {353391,292874}, {353459,291963}, {353581,291058}, {353755,290162}, {353981,289277}, + {354257,288407}, {354584,287554}, {354959,286722}, {355383,285913}, {355852,285129}, {356366,284375}, + {356923,283651}, {357521,282961}, {358159,282307}, {358833,281691}, {359541,281115}, {360282,280581}, + {361053,280091}, {361850,279647}, {362673,279250}, {363517,278901}, {363859,278775}, {363859,266490}, + {363008,266161}, {362177,265783}, {361369,265357}, {360587,264885}, {359834,264369}, {359112,263809}, + {358424,263209}, {357772,262570}, {357158,261894}, {356584,261184}, {356052,260441}, {355565,259669}, + {355123,258870}, {354728,258046}, {354382,257202}, {354085,256338}, {353839,255459}, {353644,254566}, + {353502,253664}, {353412,252756}, {353375,251843}, {353391,250930}, {353459,250020}, {353581,249115}, + {353755,248218}, {353981,247334}, {354257,246463}, {354584,245611}, {354959,244778}, {355383,243969}, + {355852,243186}, {356366,242431}, {356923,241708}, {357521,241018}, {358159,240364}, {358833,239747}, + {359541,239172}, {360282,238638}, {361053,238148}, {361850,237704}, {362673,237306}, {363517,236957}, + {363859,236831}, {363859,224547}, {363008,224217}, {362177,223839}, {361369,223413}, {360587,222942}, + {359834,222425}, {359112,221866}, {358424,221266}, {357772,220626}, {357158,219951}, {356584,219240}, + {356052,218498}, {355565,217726}, {355123,216926}, {354728,216103}, {354382,215258}, {354085,214395}, + {353839,213515}, {353644,212623}, {353502,211721}, {353412,210812}, {353375,209900}, {353391,208987}, + {353459,208076}, {353581,207171}, {353755,206275}, {353981,205390}, {354257,204520}, {354584,203667}, + {354959,202835}, {355383,202026}, {355852,201242}, {356366,200488}, {356923,199764}, {357521,199074}, + {358159,198420}, {358833,197804}, {359541,197228}, {360282,196694}, {361053,196204}, {361850,195760}, + {362673,195363}, {363517,195014}, {363859,194888}, {363859,182603}, {363008,182274}, {362177,181896}, + {361369,181470}, {360587,180998}, {359834,180482}, {359112,179922}, {358424,179322}, {357772,178683}, + {357158,178007}, {356584,177297}, {356052,176554}, {355565,175782}, {355123,174983}, {354728,174160}, + {354382,173315}, {354085,172451}, {353839,171572}, {353644,170680}, {353502,169778}, {353412,168869}, + {353375,167956}, {353391,167043}, {353459,166133}, {353581,165228}, {353755,164331}, {353981,163447}, + {354257,162576}, {354584,161724}, {354959,160891}, {355383,160082}, {355852,159299}, {356366,158544}, + {356923,157821}, {357521,157131}, {358159,156477}, {358833,155861}, {359541,155285}, {360282,154751}, + {361053,154261}, {361850,153817}, {362673,153419}, {363517,153070}, {363859,152945}, {363859,140660}, + {363008,140330}, {362177,139952}, {361369,139527}, {360587,139055}, {359834,138538}, {359112,137979}, + {358424,137379}, {357772,136740}, {357158,136064}, {356584,135353}, {356052,134611}, {355565,133839}, + {355123,133039}, {354728,132216}, {354382,131371}, {354085,130508}, {353839,129628}, {353644,128736}, + {353502,127834}, {353412,126925}, {353375,126013}, {353391,125100}, {353459,124189}, {353581,123284}, + {353755,122388}, {353981,121503}, {354257,120633}, {354584,119780}, {354959,118948}, {355383,118139}, + {355852,117355}, {356366,116601}, {356923,115877}, {357521,115187}, {358159,114533}, {358833,113917}, + {359541,113341}, {360282,112807}, {361053,112317}, {361850,111873}, {362673,111476}, {363517,111127}, + {363859,111001}, {363859,98716}, {363008,98387}, {362177,98009}, {361369,97583}, {360587,97111}, + {359834,96595}, {359112,96035}, {358424,95435}, {357772,94796}, {357158,94120}, {356584,93410}, + {356052,92667}, {355565,91895}, {355123,91096}, {354728,90273}, {354382,89428}, {354085,88564}, + {353839,87685}, {353644,86793}, {353502,85891}, {353412,84982}, {353375,84070}, {353391,83157}, + {353459,82246}, {353581,81341}, {353755,80445}, {353981,79560}, {354257,78689}, {354584,77837}, + {354959,77004}, {355383,76195}, {355852,75412}, {356366,74657}, {356923,73934}, {357521,73244}, + {358159,72590}, {358833,71974}, {359541,71398}, {360282,70864}, {361053,70374}, {361850,69930}, + {362673,69532}, {363517,69183}, {363859,69058}, {363859,56773}, {363008,56443}, {362177,56065}, + {361369,55640}, {360587,55168}, {359834,54651}, {359112,54092}, {358424,53492}, {357772,52853}, + {357158,52177}, {356584,51466}, {356052,50724}, {355565,49952}, {355123,49153}, {354728,48329}, + {354382,47484}, {354085,46621}, {353839,45741}, {353644,44849}, {353502,43947}, {353412,43039}, + {353375,42126}, {353391,41213}, {353459,40303}, {353581,39398}, {353755,38501}, {353981,37616}, + {354257,36746}, {354584,35893}, {354959,35061}, {355383,34252}, {355852,33469}, {356366,32714}, + {356923,31990}, {357521,31300}, {358159,30646}, {358833,30030}, {359541,29454}, {360282,28920}, + {361053,28431}, {361850,27986}, {362673,27589}, {363517,27240}, {363859,27114}, {363859,14829}, + {363011,14501}, {362182,14125}, {361377,13701}, {360597,13231}, {359847,12717}, {359127,12160}, + {358440,11563}, {357789,10927}, {357176,10255}, {356603,9548}, {356072,8809}, {355584,8041}, + {355142,7245}, {354746,6426}, {354398,5585}, {354273,5243}, {337794,5243}, {337465,6095}, + {337086,6926}, {336661,7734}, {336189,8515}, {335672,9269}, {335113,9990}, {334513,10679}, + {333874,11331}, {333198,11945}, {332487,12518}, {331745,13050}, {330973,13537}, {330174,13979}, + {329350,14374}, {328505,14721}, {327642,15017}, {326762,15263}, {325870,15458}, {324968,15601}, + {324060,15691}, {323147,15728}, {322234,15712}, {321324,15643}, {320419,15521}, {319522,15347}, + {318637,15122}, {317767,14845}, {316914,14519}, {316082,14143}, {315273,13720}, {314490,13250}, + {313735,12736}, {313012,12179}, {312322,11581}, {311667,10944}, {311051,10270}, {310475,9561}, + {309942,8820}, {309452,8050}, {309007,7252}, {308610,6430}, {308261,5586}, {308135,5243}, + {291656,5243}, {291327,6095}, {290949,6926}, {290523,7734}, {290051,8515}, {289535,9269}, + {288975,9990}, {288375,10679}, {287736,11331}, {287060,11945}, {286350,12518}, {285607,13050}, + {284835,13537}, {284036,13979}, {283213,14374}, {282368,14721}, {281504,15017}, {280625,15263}, + {279733,15458}, {278831,15601}, {277922,15691}, {277009,15728}, {276096,15712}, {275186,15643}, + {274281,15521}, {273384,15347}, {272500,15122}, {271629,14845}, {270777,14519}, {269944,14143}, + {269135,13720}, {268352,13250}, {267597,12736}, {266874,12179}, {266184,11581}, {265530,10944}, + {264914,10270}, {264338,9561}, {263804,8820}, {263314,8050}, {262870,7252}, {262472,6430}, + {262123,5586}, {261998,5243}, {245518,5243}, {245189,6095}, {244811,6926}, {244385,7734}, + {243913,8515}, {243397,9269}, {242838,9990}, {242237,10679}, {241598,11331}, {240922,11945}, + {240212,12518}, {239469,13050}, {238697,13537}, {237898,13979}, {237075,14374}, {236230,14721}, + {235366,15017}, {234487,15263}, {233595,15458}, {232693,15601}, {231784,15691}, {230872,15728}, + {229959,15712}, {229048,15643}, {228143,15521}, {227247,15347}, {226362,15122}, {225492,14845}, + {224639,14519}, {223806,14143}, {222997,13720}, {222214,13250}, {221459,12736}, {220736,12179}, + {220046,11581}, {219392,10944}, {218776,10270}, {218200,9561}, {217666,8820}, {217176,8050}, + {216732,7252}, {216334,6430}, {215986,5586}, {215860,5243}, {199380,5243}, {199051,6095}, + {198673,6926}, {198247,7734}, {197775,8515}, {197259,9269}, {196700,9990}, {196100,10679}, + {195460,11331}, {194784,11945}, {194074,12518}, {193332,13050}, {192559,13537}, {191760,13979}, + {190937,14374}, {190092,14721}, {189228,15017}, {188349,15263}, {187457,15458}, {186555,15601}, + {185646,15691}, {184734,15728}, {183821,15712}, {182910,15643}, {182005,15521}, {181109,15347}, + {180224,15122}, {179354,14845}, {178501,14519}, {177669,14143}, {176860,13720}, {176076,13250}, + {175322,12736}, {174598,12179}, {173908,11581}, {173254,10944}, {172638,10270}, {172062,9561}, + {171528,8820}, {171038,8050}, {170594,7252}, {170197,6430}, {169848,5586}, {169722,5243}, + {153243,5243}, {152913,6095}, {152535,6926}, {152110,7734}, {151638,8515}, {151121,9269}, + {150562,9990}, {149962,10679}, {149323,11331}, {148647,11945}, {147936,12518}, {147194,13050}, + {146422,13537}, {145623,13979}, {144799,14374}, {143954,14721}, {143091,15017}, {142211,15263}, + {141319,15458}, {140417,15601}, {139508,15691}, {138596,15728}, {137683,15712}, {136772,15643}, + {135867,15521}, {134971,15347}, {134086,15122}, {133216,14845}, {132363,14519}, {131531,14143}, + {130722,13720}, {129939,13250}, {129184,12736}, {128460,12179}, {127770,11581}, {127116,10944}, + {126500,10270}, {125924,9561}, {125390,8820}, {124901,8050}, {124456,7252}, {124059,6430}, + {123710,5586}, {123584,5243}, {107105,5243}, {106776,6095}, {106397,6926}, {105972,7734}, + {105500,8515}, {104983,9269}, {104424,9990}, {103824,10679}, {103185,11331}, {102509,11945}, + {101798,12518}, {101056,13050}, {100284,13537}, {99485,13979}, {98661,14374}, {97816,14721}, + {96953,15017}, {96073,15263}, {95181,15458}, {94279,15601}, {93371,15691}, {92458,15728}, + {91545,15712}, {90635,15643}, {89730,15521}, {88833,15347}, {87948,15122}, {87078,14845}, + {86225,14519}, {85393,14143}, {84584,13720}, {83801,13250}, {83046,12736}, {82323,12179}, + {81633,11581}, {80978,10944}, {80362,10270}, {79786,9561}, {79253,8820}, {78763,8050}, + {78318,7252}, {77921,6430}, {77572,5586}, {77446,5243}, {60967,5243}, {60638,6095}, + {60260,6926}, {59834,7734}, {59362,8515}, {58846,9269}, {58286,9990}, {57686,10679}, + {57047,11331}, {56371,11945}, {55661,12518}, {54918,13050}, {54146,13537}, {53347,13979}, + {52524,14374}, {51679,14721}, {50815,15017}, {49936,15263}, {49044,15458}, {48142,15601}, + {47233,15691}, {46320,15728}, {45407,15712}, {44497,15643}, {43592,15521}, {42695,15347}, + {41811,15122}, {40940,14845}, {40088,14519}, {39255,14143}, {38446,13720}, {37663,13250}, + {36908,12736}, {36185,12179}, {35495,11581}, {34841,10944}, {34225,10270}, {33649,9561}, + {33115,8820}, {32625,8050}, {32181,7252}, {31783,6430}, {31434,5586}, {31309,5243}, + {14829,5243}, {14501,6092}, {14125,6920}, {13701,7726}, {13231,8505}, {12717,9256}, + {12160,9976}, {11563,10662}, {10927,11313}, {10255,11926}, {9548,12499}, {8809,13031}, + {8041,13518}, {7245,13961}, {6426,14356}, {5585,14704}}, + + {{-478773,-478773}, {847875,-478773}, {847875,814320}, {-478773,814320}} }; + + Paths64 solution = InflatePaths(subject, -43.6e03, JoinType::Round, EndType::Polygon); + EXPECT_EQ(solution.size(), 2); +} + diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index d5984e22..d1ce9994 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 9 September 2023 * +* Date : 16 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -469,9 +469,7 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) return; } - if (cosA > 0.999) - DoMiter(group, path, j, k, cosA); - else if (cosA > -0.99 && (sinA * _groupDelta < 0)) + if (cosA > -0.99 && (sinA * _groupDelta < 0)) // test for concavity first (#593) { // is concave group.outPath.Add(GetPerpendic(path[j], _normals[k])); @@ -480,6 +478,8 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) group.outPath.Add(path[j]); // (#405) group.outPath.Add(GetPerpendic(path[j], _normals[j])); } + else if (cosA > 0.999) + DoMiter(group, path, j, k, cosA); else if (_joinType == JoinType.Miter) { // miter unless the angle is so acute the miter would exceeds ML diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index 1214fa58..69fa73db 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 9 September 2023 * +* Date : 16 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -892,9 +892,8 @@ procedure TClipperOffset.OffsetPoint(j: Integer; var k: integer); Exit; end; - if (cosA > 0.999) then // almost straight - less than 2.5 degree (#424, #526) - DoMiter(j, k, cosA) - else if (cosA > -0.99) and (sinA * fGroupDelta < 0) then + //test for concavity first (#593) + if (cosA > -0.99) and (sinA * fGroupDelta < 0) then begin // is concave AddPoint(GetPerpendic(fInPath[j], fNorms[k], fGroupDelta)); @@ -903,6 +902,9 @@ procedure TClipperOffset.OffsetPoint(j: Integer; var k: integer); AddPoint(fInPath[j]); // (#405) AddPoint(GetPerpendic(fInPath[j], fNorms[j], fGroupDelta)); end + else if (cosA > 0.999) then + // almost straight - less than 2.5 degree (#424, #526) + DoMiter(j, k, cosA) else if (fJoinType = jtMiter) then begin // miter unless the angle is so acute the miter would exceeds ML From 83b0ccc97a72d65bc55b5a34ee04fd634b9def08 Mon Sep 17 00:00:00 2001 From: angusj Date: Tue, 19 Sep 2023 06:41:20 +1000 Subject: [PATCH 39/95] Fixed bug in 180deg rounding (C++ & c#) //#593 --- CPP/Clipper2Lib/src/clipper.offset.cpp | 18 +- CPP/Tests/TestOffsets.cpp | 473 +++++++++++++------------ CSharp/Clipper2Lib/Clipper.Offset.cs | 17 +- 3 files changed, 265 insertions(+), 243 deletions(-) diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index 496ad9a7..8aa8c58f 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 September 2023 * +* Date : 19 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -284,20 +284,16 @@ void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k #else group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); #endif - if (angle > -PI + 0.01) // avoid 180deg concave + 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 { - int steps = static_cast(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456 - for (int i = 1; i < steps; ++i) // ie 1 less than steps - { - offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y, - offsetVec.x * step_sin_ + offsetVec.y * step_cos_); + offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y, + offsetVec.x * step_sin_ + offsetVec.y * step_cos_); #ifdef USINGZ - group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); + group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); #else - group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); + group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); #endif - - } } group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); } diff --git a/CPP/Tests/TestOffsets.cpp b/CPP/Tests/TestOffsets.cpp index dd713364..6fa5e9db 100644 --- a/CPP/Tests/TestOffsets.cpp +++ b/CPP/Tests/TestOffsets.cpp @@ -148,230 +148,259 @@ TEST(Clipper2Tests, TestOffsets4) // see #482 EXPECT_GT(solution[0].size(), 6); } -TEST(Clipper2Tests, TestOffsets5) // see #593 +TEST(Clipper2Tests, TestOffsets5) // modified from #593 (tests offset clean up) { Paths64 subject = { - {{5243,14829}, {5243,27114}, {6095,27443}, {6926,27822}, {7734,28247}, {8515,28719}, - {9269,29236}, {9990,29795}, {10679,30395}, {11331,31034}, {11945,31710}, {12518,32421}, - {13050,33163}, {13537,33935}, {13979,34734}, {14374,35558}, {14721,36403}, {15017,37266}, - {15263,38146}, {15458,39038}, {15601,39940}, {15691,40848}, {15728,41761}, {15712,42674}, - {15643,43584}, {15521,44489}, {15347,45386}, {15122,46271}, {14845,47141}, {14519,47994}, - {14143,48826}, {13720,49635}, {13250,50418}, {12736,51173}, {12179,51897}, {11581,52587}, - {10944,53241}, {10270,53857}, {9561,54433}, {8820,54966}, {8050,55456}, {7252,55901}, - {6430,56298}, {5586,56647}, {5243,56773}, {5243,69058}, {6095,69387}, {6926,69765}, - {7734,70191}, {8515,70663}, {9269,71179}, {9990,71738}, {10679,72339}, {11331,72978}, - {11945,73654}, {12518,74364}, {13050,75107}, {13537,75879}, {13979,76678}, {14374,77501}, - {14721,78346}, {15017,79210}, {15263,80089}, {15458,80981}, {15601,81883}, {15691,82792}, - {15728,83704}, {15712,84617}, {15643,85528}, {15521,86433}, {15347,87329}, {15122,88214}, - {14845,89084}, {14519,89937}, {14143,90769}, {13720,91579}, {13250,92362}, {12736,93116}, - {12179,93840}, {11581,94530}, {10944,95184}, {10270,95800}, {9561,96376}, {8820,96910}, - {8050,97400}, {7252,97844}, {6430,98241}, {5586,98590}, {5243,98716}, {5243,111001}, - {6095,111330}, {6926,111708}, {7734,112134}, {8515,112606}, {9269,113123}, {9990,113682}, - {10679,114282}, {11331,114921}, {11945,115597}, {12518,116308}, {13050,117050}, {13537,117822}, - {13979,118621}, {14374,119445}, {14721,120290}, {15017,121153}, {15263,122033}, {15458,122925}, - {15601,123827}, {15691,124735}, {15728,125648}, {15712,126561}, {15643,127471}, {15521,128376}, - {15347,129273}, {15122,130158}, {14845,131028}, {14519,131881}, {14143,132713}, {13720,133522}, - {13250,134305}, {12736,135060}, {12179,135783}, {11581,136473}, {10944,137128}, {10270,137744}, - {9561,138320}, {8820,138853}, {8050,139343}, {7252,139788}, {6430,140185}, {5586,140534}, - {5243,140660}, {5243,152945}, {6095,153274}, {6926,153652}, {7734,154078}, {8515,154550}, - {9269,155066}, {9990,155625}, {10679,156225}, {11331,156865}, {11945,157541}, {12518,158251}, - {13050,158993}, {13537,159766}, {13979,160565}, {14374,161388}, {14721,162233}, {15017,163097}, - {15263,163976}, {15458,164868}, {15601,165770}, {15691,166679}, {15728,167591}, {15712,168504}, - {15643,169415}, {15521,170320}, {15347,171216}, {15122,172101}, {14845,172971}, {14519,173824}, - {14143,174656}, {13720,175465}, {13250,176249}, {12736,177003}, {12179,177727}, {11581,178417}, - {10944,179071}, {10270,179687}, {9561,180263}, {8820,180797}, {8050,181287}, {7252,181731}, - {6430,182128}, {5586,182477}, {5243,182603}, {5243,194888}, {6095,195217}, {6926,195595}, - {7734,196021}, {8515,196493}, {9269,197009}, {9990,197569}, {10679,198169}, {11331,198808}, - {11945,199484}, {12518,200194}, {13050,200937}, {13537,201709}, {13979,202508}, {14374,203332}, - {14721,204176}, {15017,205040}, {15263,205919}, {15458,206812}, {15601,207714}, {15691,208622}, - {15728,209535}, {15712,210448}, {15643,211358}, {15521,212263}, {15347,213160}, {15122,214044}, - {14845,214915}, {14519,215767}, {14143,216600}, {13720,217409}, {13250,218192}, {12736,218947}, - {12179,219670}, {11581,220360}, {10944,221014}, {10270,221631}, {9561,222206}, {8820,222740}, - {8050,223230}, {7252,223675}, {6430,224072}, {5586,224421}, {5243,224547}, {5243,236831}, - {6095,237161}, {6926,237539}, {7734,237965}, {8515,238436}, {9269,238953}, {9990,239512}, - {10679,240112}, {11331,240752}, {11945,241427}, {12518,242138}, {13050,242880}, {13537,243653}, - {13979,244452}, {14374,245275}, {14721,246120}, {15017,246984}, {15263,247863}, {15458,248755}, - {15601,249657}, {15691,250566}, {15728,251478}, {15712,252391}, {15643,253302}, {15521,254207}, - {15347,255103}, {15122,255988}, {14845,256858}, {14519,257711}, {14143,258543}, {13720,259352}, - {13250,260136}, {12736,260890}, {12179,261614}, {11581,262304}, {10944,262958}, {10270,263574}, - {9561,264150}, {8820,264684}, {8050,265174}, {7252,265618}, {6430,266015}, {5586,266364}, - {5243,266490}, {5243,278775}, {6095,279104}, {6926,279482}, {7734,279908}, {8515,280380}, - {9269,280896}, {9990,281456}, {10679,282056}, {11331,282695}, {11945,283371}, {12518,284081}, - {13050,284824}, {13537,285596}, {13979,286395}, {14374,287218}, {14721,288063}, {15017,288927}, - {15263,289806}, {15458,290698}, {15601,291600}, {15691,292509}, {15728,293422}, {15712,294335}, - {15643,295245}, {15521,296150}, {15347,297047}, {15122,297931}, {14845,298802}, {14519,299654}, - {14143,300487}, {13720,301296}, {13250,302079}, {12736,302834}, {12179,303557}, {11581,304247}, - {10944,304901}, {10270,305517}, {9561,306093}, {8820,306627}, {8050,307117}, {7252,307561}, - {6430,307959}, {5586,308308}, {5243,308433}, {5243,320718}, {6092,321046}, {6920,321423}, - {7726,321847}, {8505,322317}, {9256,322831}, {9976,323387}, {10662,323984}, {11313,324620}, - {11926,325293}, {12499,326000}, {13031,326739}, {13518,327507}, {13961,328302}, {14356,329122}, - {14704,329963}, {14829,330305}, {31309,330305}, {31638,329453}, {32016,328622}, {32442,327814}, - {32914,327032}, {33430,326279}, {33989,325557}, {34589,324869}, {35229,324217}, {35905,323603}, - {36615,323029}, {37357,322498}, {38130,322010}, {38929,321568}, {39752,321173}, {40597,320827}, - {41461,320530}, {42340,320284}, {43232,320090}, {44134,319947}, {45043,319857}, {45955,319820}, - {46868,319836}, {47779,319905}, {48684,320026}, {49580,320200}, {50465,320426}, {51335,320702}, - {52188,321029}, {53020,321405}, {53829,321828}, {54613,322297}, {55367,322811}, {56091,323369}, - {56781,323967}, {57435,324604}, {58051,325278}, {58627,325986}, {59161,326727}, {59651,327498}, - {60095,328296}, {60492,329118}, {60841,329962}, {60967,330305}, {77446,330305}, {77776,329453}, - {78154,328622}, {78579,327814}, {79051,327032}, {79568,326279}, {80127,325557}, {80727,324869}, - {81366,324217}, {82042,323603}, {82753,323029}, {83495,322498}, {84267,322010}, {85066,321568}, - {85890,321173}, {86735,320827}, {87598,320530}, {88478,320284}, {89370,320090}, {90272,319947}, - {91181,319857}, {92093,319820}, {93006,319836}, {93917,319905}, {94822,320026}, {95718,320200}, - {96603,320426}, {97473,320702}, {98326,321029}, {99158,321405}, {99967,321828}, {100750,322297}, - {101505,322811}, {102229,323369}, {102919,323967}, {103573,324604}, {104189,325278}, {104765,325986}, - {105299,326727}, {105788,327498}, {106233,328296}, {106630,329118}, {106979,329962}, {107105,330305}, - {123584,330305}, {123913,329453}, {124292,328622}, {124717,327814}, {125189,327032}, {125706,326279}, - {126265,325557}, {126865,324869}, {127504,324217}, {128180,323603}, {128891,323029}, {129633,322498}, - {130405,322010}, {131204,321568}, {132028,321173}, {132873,320827}, {133736,320530}, {134616,320284}, - {135508,320090}, {136410,319947}, {137318,319857}, {138231,319820}, {139144,319836}, {140054,319905}, - {140959,320026}, {141856,320200}, {142741,320426}, {143611,320702}, {144464,321029}, {145296,321405}, - {146105,321828}, {146888,322297}, {147643,322811}, {148366,323369}, {149056,323967}, {149711,324604}, - {150327,325278}, {150903,325986}, {151436,326727}, {151926,327498}, {152371,328296}, {152768,329118}, - {153117,329962}, {153243,330305}, {169722,330305}, {170051,329453}, {170429,328622}, {170855,327814}, - {171327,327032}, {171843,326279}, {172403,325557}, {173003,324869}, {173642,324217}, {174318,323603}, - {175028,323029}, {175771,322498}, {176543,322010}, {177342,321568}, {178165,321173}, {179010,320827}, - {179874,320530}, {180753,320284}, {181646,320090}, {182547,319947}, {183456,319857}, {184369,319820}, - {185282,319836}, {186192,319905}, {187097,320026}, {187994,320200}, {188878,320426}, {189749,320702}, - {190601,321029}, {191434,321405}, {192243,321828}, {193026,322297}, {193781,322811}, {194504,323369}, - {195194,323967}, {195848,324604}, {196464,325278}, {197040,325986}, {197574,326727}, {198064,327498}, - {198508,328296}, {198906,329118}, {199255,329962}, {199380,330305}, {215860,330305}, {216189,329453}, - {216567,328622}, {216993,327814}, {217465,327032}, {217981,326279}, {218540,325557}, {219141,324869}, - {219780,324217}, {220456,323603}, {221166,323029}, {221909,322498}, {222681,322010}, {223480,321568}, - {224303,321173}, {225148,320827}, {226012,320530}, {226891,320284}, {227783,320090}, {228685,319947}, - {229594,319857}, {230506,319820}, {231419,319836}, {232330,319905}, {233235,320026}, {234131,320200}, - {235016,320426}, {235886,320702}, {236739,321029}, {237572,321405}, {238381,321828}, {239164,322297}, - {239919,322811}, {240642,323369}, {241332,323967}, {241986,324604}, {242602,325278}, {243178,325986}, - {243712,326727}, {244202,327498}, {244646,328296}, {245044,329118}, {245393,329962}, {245518,330305}, - {261998,330305}, {262327,329453}, {262705,328622}, {263131,327814}, {263603,327032}, {264119,326279}, - {264678,325557}, {265278,324869}, {265918,324217}, {266594,323603}, {267304,323029}, {268046,322498}, - {268819,322010}, {269618,321568}, {270441,321173}, {271286,320827}, {272150,320530}, {273029,320284}, - {273921,320090}, {274823,319947}, {275732,319857}, {276644,319820}, {277557,319836}, {278468,319905}, - {279373,320026}, {280269,320200}, {281154,320426}, {282024,320702}, {282877,321029}, {283709,321405}, - {284518,321828}, {285302,322297}, {286056,322811}, {286780,323369}, {287470,323967}, {288124,324604}, - {288740,325278}, {289316,325986}, {289850,326727}, {290340,327498}, {290784,328296}, {291181,329118}, - {291530,329962}, {291656,330305}, {308135,330305}, {308465,329453}, {308843,328622}, {309268,327814}, - {309740,327032}, {310257,326279}, {310816,325557}, {311416,324869}, {312055,324217}, {312731,323603}, - {313442,323029}, {314184,322498}, {314956,322010}, {315755,321568}, {316579,321173}, {317424,320827}, - {318287,320530}, {319167,320284}, {320059,320090}, {320961,319947}, {321870,319857}, {322782,319820}, - {323695,319836}, {324606,319905}, {325511,320026}, {326407,320200}, {327292,320426}, {328162,320702}, - {329015,321029}, {329847,321405}, {330656,321828}, {331440,322297}, {332194,322811}, {332918,323369}, - {333608,323967}, {334262,324604}, {334878,325278}, {335454,325986}, {335988,326727}, {336477,327498}, - {336922,328296}, {337319,329118}, {337668,329962}, {337794,330305}, {354273,330305}, {354601,329456}, - {354978,328627}, {355402,327822}, {355871,327043}, {356385,326292}, {356942,325572}, {357539,324885}, - {358175,324234}, {358848,323621}, {359555,323048}, {360294,322517}, {361062,322029}, {361857,321587}, - {362677,321191}, {363518,320844}, {363859,320718}, {363859,308433}, {363008,308104}, {362177,307726}, - {361369,307300}, {360587,306828}, {359834,306312}, {359112,305753}, {358424,305153}, {357772,304513}, - {357158,303837}, {356584,303127}, {356052,302385}, {355565,301612}, {355123,300813}, {354728,299990}, - {354382,299145}, {354085,298281}, {353839,297402}, {353644,296510}, {353502,295608}, {353412,294699}, - {353375,293787}, {353391,292874}, {353459,291963}, {353581,291058}, {353755,290162}, {353981,289277}, - {354257,288407}, {354584,287554}, {354959,286722}, {355383,285913}, {355852,285129}, {356366,284375}, - {356923,283651}, {357521,282961}, {358159,282307}, {358833,281691}, {359541,281115}, {360282,280581}, - {361053,280091}, {361850,279647}, {362673,279250}, {363517,278901}, {363859,278775}, {363859,266490}, - {363008,266161}, {362177,265783}, {361369,265357}, {360587,264885}, {359834,264369}, {359112,263809}, - {358424,263209}, {357772,262570}, {357158,261894}, {356584,261184}, {356052,260441}, {355565,259669}, - {355123,258870}, {354728,258046}, {354382,257202}, {354085,256338}, {353839,255459}, {353644,254566}, - {353502,253664}, {353412,252756}, {353375,251843}, {353391,250930}, {353459,250020}, {353581,249115}, - {353755,248218}, {353981,247334}, {354257,246463}, {354584,245611}, {354959,244778}, {355383,243969}, - {355852,243186}, {356366,242431}, {356923,241708}, {357521,241018}, {358159,240364}, {358833,239747}, - {359541,239172}, {360282,238638}, {361053,238148}, {361850,237704}, {362673,237306}, {363517,236957}, - {363859,236831}, {363859,224547}, {363008,224217}, {362177,223839}, {361369,223413}, {360587,222942}, - {359834,222425}, {359112,221866}, {358424,221266}, {357772,220626}, {357158,219951}, {356584,219240}, - {356052,218498}, {355565,217726}, {355123,216926}, {354728,216103}, {354382,215258}, {354085,214395}, - {353839,213515}, {353644,212623}, {353502,211721}, {353412,210812}, {353375,209900}, {353391,208987}, - {353459,208076}, {353581,207171}, {353755,206275}, {353981,205390}, {354257,204520}, {354584,203667}, - {354959,202835}, {355383,202026}, {355852,201242}, {356366,200488}, {356923,199764}, {357521,199074}, - {358159,198420}, {358833,197804}, {359541,197228}, {360282,196694}, {361053,196204}, {361850,195760}, - {362673,195363}, {363517,195014}, {363859,194888}, {363859,182603}, {363008,182274}, {362177,181896}, - {361369,181470}, {360587,180998}, {359834,180482}, {359112,179922}, {358424,179322}, {357772,178683}, - {357158,178007}, {356584,177297}, {356052,176554}, {355565,175782}, {355123,174983}, {354728,174160}, - {354382,173315}, {354085,172451}, {353839,171572}, {353644,170680}, {353502,169778}, {353412,168869}, - {353375,167956}, {353391,167043}, {353459,166133}, {353581,165228}, {353755,164331}, {353981,163447}, - {354257,162576}, {354584,161724}, {354959,160891}, {355383,160082}, {355852,159299}, {356366,158544}, - {356923,157821}, {357521,157131}, {358159,156477}, {358833,155861}, {359541,155285}, {360282,154751}, - {361053,154261}, {361850,153817}, {362673,153419}, {363517,153070}, {363859,152945}, {363859,140660}, - {363008,140330}, {362177,139952}, {361369,139527}, {360587,139055}, {359834,138538}, {359112,137979}, - {358424,137379}, {357772,136740}, {357158,136064}, {356584,135353}, {356052,134611}, {355565,133839}, - {355123,133039}, {354728,132216}, {354382,131371}, {354085,130508}, {353839,129628}, {353644,128736}, - {353502,127834}, {353412,126925}, {353375,126013}, {353391,125100}, {353459,124189}, {353581,123284}, - {353755,122388}, {353981,121503}, {354257,120633}, {354584,119780}, {354959,118948}, {355383,118139}, - {355852,117355}, {356366,116601}, {356923,115877}, {357521,115187}, {358159,114533}, {358833,113917}, - {359541,113341}, {360282,112807}, {361053,112317}, {361850,111873}, {362673,111476}, {363517,111127}, - {363859,111001}, {363859,98716}, {363008,98387}, {362177,98009}, {361369,97583}, {360587,97111}, - {359834,96595}, {359112,96035}, {358424,95435}, {357772,94796}, {357158,94120}, {356584,93410}, - {356052,92667}, {355565,91895}, {355123,91096}, {354728,90273}, {354382,89428}, {354085,88564}, - {353839,87685}, {353644,86793}, {353502,85891}, {353412,84982}, {353375,84070}, {353391,83157}, - {353459,82246}, {353581,81341}, {353755,80445}, {353981,79560}, {354257,78689}, {354584,77837}, - {354959,77004}, {355383,76195}, {355852,75412}, {356366,74657}, {356923,73934}, {357521,73244}, - {358159,72590}, {358833,71974}, {359541,71398}, {360282,70864}, {361053,70374}, {361850,69930}, - {362673,69532}, {363517,69183}, {363859,69058}, {363859,56773}, {363008,56443}, {362177,56065}, - {361369,55640}, {360587,55168}, {359834,54651}, {359112,54092}, {358424,53492}, {357772,52853}, - {357158,52177}, {356584,51466}, {356052,50724}, {355565,49952}, {355123,49153}, {354728,48329}, - {354382,47484}, {354085,46621}, {353839,45741}, {353644,44849}, {353502,43947}, {353412,43039}, - {353375,42126}, {353391,41213}, {353459,40303}, {353581,39398}, {353755,38501}, {353981,37616}, - {354257,36746}, {354584,35893}, {354959,35061}, {355383,34252}, {355852,33469}, {356366,32714}, - {356923,31990}, {357521,31300}, {358159,30646}, {358833,30030}, {359541,29454}, {360282,28920}, - {361053,28431}, {361850,27986}, {362673,27589}, {363517,27240}, {363859,27114}, {363859,14829}, - {363011,14501}, {362182,14125}, {361377,13701}, {360597,13231}, {359847,12717}, {359127,12160}, - {358440,11563}, {357789,10927}, {357176,10255}, {356603,9548}, {356072,8809}, {355584,8041}, - {355142,7245}, {354746,6426}, {354398,5585}, {354273,5243}, {337794,5243}, {337465,6095}, - {337086,6926}, {336661,7734}, {336189,8515}, {335672,9269}, {335113,9990}, {334513,10679}, - {333874,11331}, {333198,11945}, {332487,12518}, {331745,13050}, {330973,13537}, {330174,13979}, - {329350,14374}, {328505,14721}, {327642,15017}, {326762,15263}, {325870,15458}, {324968,15601}, - {324060,15691}, {323147,15728}, {322234,15712}, {321324,15643}, {320419,15521}, {319522,15347}, - {318637,15122}, {317767,14845}, {316914,14519}, {316082,14143}, {315273,13720}, {314490,13250}, - {313735,12736}, {313012,12179}, {312322,11581}, {311667,10944}, {311051,10270}, {310475,9561}, - {309942,8820}, {309452,8050}, {309007,7252}, {308610,6430}, {308261,5586}, {308135,5243}, - {291656,5243}, {291327,6095}, {290949,6926}, {290523,7734}, {290051,8515}, {289535,9269}, - {288975,9990}, {288375,10679}, {287736,11331}, {287060,11945}, {286350,12518}, {285607,13050}, - {284835,13537}, {284036,13979}, {283213,14374}, {282368,14721}, {281504,15017}, {280625,15263}, - {279733,15458}, {278831,15601}, {277922,15691}, {277009,15728}, {276096,15712}, {275186,15643}, - {274281,15521}, {273384,15347}, {272500,15122}, {271629,14845}, {270777,14519}, {269944,14143}, - {269135,13720}, {268352,13250}, {267597,12736}, {266874,12179}, {266184,11581}, {265530,10944}, - {264914,10270}, {264338,9561}, {263804,8820}, {263314,8050}, {262870,7252}, {262472,6430}, - {262123,5586}, {261998,5243}, {245518,5243}, {245189,6095}, {244811,6926}, {244385,7734}, - {243913,8515}, {243397,9269}, {242838,9990}, {242237,10679}, {241598,11331}, {240922,11945}, - {240212,12518}, {239469,13050}, {238697,13537}, {237898,13979}, {237075,14374}, {236230,14721}, - {235366,15017}, {234487,15263}, {233595,15458}, {232693,15601}, {231784,15691}, {230872,15728}, - {229959,15712}, {229048,15643}, {228143,15521}, {227247,15347}, {226362,15122}, {225492,14845}, - {224639,14519}, {223806,14143}, {222997,13720}, {222214,13250}, {221459,12736}, {220736,12179}, - {220046,11581}, {219392,10944}, {218776,10270}, {218200,9561}, {217666,8820}, {217176,8050}, - {216732,7252}, {216334,6430}, {215986,5586}, {215860,5243}, {199380,5243}, {199051,6095}, - {198673,6926}, {198247,7734}, {197775,8515}, {197259,9269}, {196700,9990}, {196100,10679}, - {195460,11331}, {194784,11945}, {194074,12518}, {193332,13050}, {192559,13537}, {191760,13979}, - {190937,14374}, {190092,14721}, {189228,15017}, {188349,15263}, {187457,15458}, {186555,15601}, - {185646,15691}, {184734,15728}, {183821,15712}, {182910,15643}, {182005,15521}, {181109,15347}, - {180224,15122}, {179354,14845}, {178501,14519}, {177669,14143}, {176860,13720}, {176076,13250}, - {175322,12736}, {174598,12179}, {173908,11581}, {173254,10944}, {172638,10270}, {172062,9561}, - {171528,8820}, {171038,8050}, {170594,7252}, {170197,6430}, {169848,5586}, {169722,5243}, - {153243,5243}, {152913,6095}, {152535,6926}, {152110,7734}, {151638,8515}, {151121,9269}, - {150562,9990}, {149962,10679}, {149323,11331}, {148647,11945}, {147936,12518}, {147194,13050}, - {146422,13537}, {145623,13979}, {144799,14374}, {143954,14721}, {143091,15017}, {142211,15263}, - {141319,15458}, {140417,15601}, {139508,15691}, {138596,15728}, {137683,15712}, {136772,15643}, - {135867,15521}, {134971,15347}, {134086,15122}, {133216,14845}, {132363,14519}, {131531,14143}, - {130722,13720}, {129939,13250}, {129184,12736}, {128460,12179}, {127770,11581}, {127116,10944}, - {126500,10270}, {125924,9561}, {125390,8820}, {124901,8050}, {124456,7252}, {124059,6430}, - {123710,5586}, {123584,5243}, {107105,5243}, {106776,6095}, {106397,6926}, {105972,7734}, - {105500,8515}, {104983,9269}, {104424,9990}, {103824,10679}, {103185,11331}, {102509,11945}, - {101798,12518}, {101056,13050}, {100284,13537}, {99485,13979}, {98661,14374}, {97816,14721}, - {96953,15017}, {96073,15263}, {95181,15458}, {94279,15601}, {93371,15691}, {92458,15728}, - {91545,15712}, {90635,15643}, {89730,15521}, {88833,15347}, {87948,15122}, {87078,14845}, - {86225,14519}, {85393,14143}, {84584,13720}, {83801,13250}, {83046,12736}, {82323,12179}, - {81633,11581}, {80978,10944}, {80362,10270}, {79786,9561}, {79253,8820}, {78763,8050}, - {78318,7252}, {77921,6430}, {77572,5586}, {77446,5243}, {60967,5243}, {60638,6095}, - {60260,6926}, {59834,7734}, {59362,8515}, {58846,9269}, {58286,9990}, {57686,10679}, - {57047,11331}, {56371,11945}, {55661,12518}, {54918,13050}, {54146,13537}, {53347,13979}, - {52524,14374}, {51679,14721}, {50815,15017}, {49936,15263}, {49044,15458}, {48142,15601}, - {47233,15691}, {46320,15728}, {45407,15712}, {44497,15643}, {43592,15521}, {42695,15347}, - {41811,15122}, {40940,14845}, {40088,14519}, {39255,14143}, {38446,13720}, {37663,13250}, - {36908,12736}, {36185,12179}, {35495,11581}, {34841,10944}, {34225,10270}, {33649,9561}, - {33115,8820}, {32625,8050}, {32181,7252}, {31783,6430}, {31434,5586}, {31309,5243}, - {14829,5243}, {14501,6092}, {14125,6920}, {13701,7726}, {13231,8505}, {12717,9256}, - {12160,9976}, {11563,10662}, {10927,11313}, {10255,11926}, {9548,12499}, {8809,13031}, - {8041,13518}, {7245,13961}, {6426,14356}, {5585,14704}}, - - {{-478773,-478773}, {847875,-478773}, {847875,814320}, {-478773,814320}} }; - - Paths64 solution = InflatePaths(subject, -43.6e03, JoinType::Round, EndType::Polygon); + {{524,1483}, {524,2711}, {610,2744}, {693,2782}, {773,2825}, {852,2872}, + {927,2924}, {999,2980}, {1068,3040}, {1133,3103}, {1195,3171}, {1252,3242}, + {1305,3316}, {1354,3394}, {1398,3473}, {1437,3556}, {1472,3640}, {1502,3727}, + {1526,3815}, {1546,3904}, {1560,3994}, {1569,4085}, {1573,4176}, {1571,4267}, + {1564,4358}, {1552,4449}, {1535,4539}, {1512,4627}, {1485,4714}, {1452,4799}, + {1414,4883}, {1372,4964}, {1325,5042}, {1274,5117}, {1218,5190}, {1158,5259}, + {1094,5324}, {1027,5386}, {956,5443}, {882,5497}, {805,5546}, {725,5590}, + {643,5630}, {559,5665}, {524,5677}, {524,6906}, {610,6939}, {693,6977}, + {773,7019}, {852,7066}, {927,7118}, {999,7174}, {1068,7234}, {1133,7298}, + {1195,7365}, {1252,7436}, {1305,7511}, {1354,7588}, {1398,7668}, {1437,7750}, + {1472,7835}, {1502,7921}, {1526,8009}, {1546,8098}, {1560,8188}, {1569,8279}, + {1573,8370}, {1571,8462}, {1564,8553}, {1552,8643}, {1535,8733}, {1512,8821}, + {1485,8908}, {1452,8994}, {1414,9077}, {1372,9158}, {1325,9236}, {1274,9312}, + {1218,9384}, {1158,9453}, {1094,9518}, {1027,9580}, {956,9638}, {882,9691}, + {805,9740}, {725,9784}, {643,9824}, {559,9859}, {524,9872}, {524,11100}, + {610,11133}, {693,11171}, {773,11213}, {852,11261}, {927,11312}, {999,11368}, + {1068,11428}, {1133,11492}, {1195,11560}, {1252,11631}, {1305,11705}, {1354,11782}, + {1398,11862}, {1437,11945}, {1472,12029}, {1502,12115}, {1526,12203}, {1546,12293}, + {1560,12383}, {1569,12474}, {1573,12565}, {1571,12656}, {1564,12747}, {1552,12838}, + {1535,12927}, {1512,13016}, {1485,13103}, {1452,13188}, {1414,13271}, {1372,13352}, + {1325,13431}, {1274,13506}, {1218,13578}, {1158,13647}, {1094,13713}, {1027,13774}, + {956,13832}, {882,13885}, {805,13934}, {725,13979}, {643,14019}, {559,14053}, + {524,14066}, {524,15295}, {610,15327}, {693,15365}, {773,15408}, {852,15455}, + {927,15507}, {999,15563}, {1068,15623}, {1133,15687}, {1195,15754}, {1252,15825}, + {1305,15899}, {1354,15977}, {1398,16057}, {1437,16139}, {1472,16223}, {1502,16310}, + {1526,16398}, {1546,16487}, {1560,16577}, {1569,16668}, {1573,16759}, {1571,16850}, + {1564,16942}, {1552,17032}, {1535,17122}, {1512,17210}, {1485,17297}, {1452,17382}, + {1414,17466}, {1372,17547}, {1325,17625}, {1274,17700}, {1218,17773}, {1158,17842}, + {1094,17907}, {1027,17969}, {956,18026}, {882,18080}, {805,18129}, {725,18173}, + {643,18213}, {559,18248}, {524,18260}, {524,19489}, {610,19522}, {693,19560}, + {773,19602}, {852,19649}, {927,19701}, {999,19757}, {1068,19817}, {1133,19881}, + {1195,19948}, {1252,20019}, {1305,20094}, {1354,20171}, {1398,20251}, {1437,20333}, + {1472,20418}, {1502,20504}, {1526,20592}, {1546,20681}, {1560,20771}, {1569,20862}, + {1573,20954}, {1571,21045}, {1564,21136}, {1552,21226}, {1535,21316}, {1512,21404}, + {1485,21492}, {1452,21577}, {1414,21660}, {1372,21741}, {1325,21819}, {1274,21895}, + {1218,21967}, {1158,22036}, {1094,22101}, {1027,22163}, {956,22221}, {882,22274}, + {805,22323}, {725,22368}, {643,22407}, {559,22442}, {524,22455}, {524,23683}, + {610,23716}, {693,23754}, {773,23797}, {852,23844}, {927,23895}, {999,23951}, + {1068,24011}, {1133,24075}, {1195,24143}, {1252,24214}, {1305,24288}, {1354,24365}, + {1398,24445}, {1437,24528}, {1472,24612}, {1502,24698}, {1526,24786}, {1546,24876}, + {1560,24966}, {1569,25057}, {1573,25148}, {1571,25239}, {1564,25330}, {1552,25421}, + {1535,25510}, {1512,25599}, {1485,25686}, {1452,25771}, {1414,25854}, {1372,25935}, + {1325,26014}, {1274,26089}, {1218,26161}, {1158,26230}, {1094,26296}, {1027,26357}, + {956,26415}, {882,26468}, {805,26517}, {725,26562}, {643,26602}, {559,26636}, + {524,26649}, {524,27878}, {610,27910}, {693,27948}, {773,27991}, {852,28038}, + {927,28090}, {999,28146}, {1068,28206}, {1133,28270}, {1195,28337}, {1252,28408}, + {1305,28482}, {1354,28560}, {1398,28640}, {1437,28722}, {1472,28806}, {1502,28893}, + {1526,28981}, {1546,29070}, {1560,29160}, {1569,29251}, {1573,29342}, {1571,29434}, + {1564,29525}, {1552,29615}, {1535,29705}, {1512,29793}, {1485,29880}, {1452,29965}, + {1414,30049}, {1372,30130}, {1325,30208}, {1274,30283}, {1218,30356}, {1158,30425}, + {1094,30490}, {1027,30552}, {956,30609}, {882,30663}, {805,30712}, {725,30756}, + {643,30796}, {559,30831}, {524,30843}, {524,32072}, {609,32105}, {692,32142}, + {773,32185}, {851,32232}, {926,32283}, {998,32339}, {1066,32398}, {1131,32462}, + {1193,32529}, {1250,32600}, {1303,32674}, {1352,32751}, {1396,32830}, {1436,32912}, + {1470,32996}, {1483,33031}, {3131,33031}, {3164,32945}, {3202,32862}, {3244,32781}, + {3291,32703}, {3343,32628}, {3399,32556}, {3459,32487}, {3523,32422}, {3591,32360}, + {3662,32303}, {3736,32250}, {3813,32201}, {3893,32157}, {3975,32117}, {4060,32083}, + {4146,32053}, {4234,32028}, {4323,32009}, {4413,31995}, {4504,31986}, {4596,31982}, + {4687,31984}, {4778,31991}, {4868,32003}, {4958,32020}, {5047,32043}, {5134,32070}, + {5219,32103}, {5302,32141}, {5383,32183}, {5461,32230}, {5537,32281}, {5609,32337}, + {5678,32397}, {5744,32460}, {5805,32528}, {5863,32599}, {5916,32673}, {5965,32750}, + {6010,32830}, {6049,32912}, {6084,32996}, {6097,33031}, {7745,33031}, {7778,32945}, + {7815,32862}, {7858,32781}, {7905,32703}, {7957,32628}, {8013,32556}, {8073,32487}, + {8137,32422}, {8204,32360}, {8275,32303}, {8350,32250}, {8427,32201}, {8507,32157}, + {8589,32117}, {8674,32083}, {8760,32053}, {8848,32028}, {8937,32009}, {9027,31995}, + {9118,31986}, {9209,31982}, {9301,31984}, {9392,31991}, {9482,32003}, {9572,32020}, + {9660,32043}, {9747,32070}, {9833,32103}, {9916,32141}, {9997,32183}, {10075,32230}, + {10151,32281}, {10223,32337}, {10292,32397}, {10357,32460}, {10419,32528}, {10477,32599}, + {10530,32673}, {10579,32750}, {10623,32830}, {10663,32912}, {10698,32996}, {10711,33031}, + {12358,33031}, {12391,32945}, {12429,32862}, {12472,32781}, {12519,32703}, {12571,32628}, + {12627,32556}, {12687,32487}, {12750,32422}, {12818,32360}, {12889,32303}, {12963,32250}, + {13041,32201}, {13120,32157}, {13203,32117}, {13287,32083}, {13374,32053}, {13462,32028}, + {13551,32009}, {13641,31995}, {13732,31986}, {13823,31982}, {13914,31984}, {14005,31991}, + {14096,32003}, {14186,32020}, {14274,32043}, {14361,32070}, {14446,32103}, {14530,32141}, + {14611,32183}, {14689,32230}, {14764,32281}, {14837,32337}, {14906,32397}, {14971,32460}, + {15033,32528}, {15090,32599}, {15144,32673}, {15193,32750}, {15237,32830}, {15277,32912}, + {15312,32996}, {15324,33031}, {16972,33031}, {17005,32945}, {17043,32862}, {17086,32781}, + {17133,32703}, {17184,32628}, {17240,32556}, {17300,32487}, {17364,32422}, {17432,32360}, + {17503,32303}, {17577,32250}, {17654,32201}, {17734,32157}, {17817,32117}, {17901,32083}, + {17987,32053}, {18075,32028}, {18165,32009}, {18255,31995}, {18346,31986}, {18437,31982}, + {18528,31984}, {18619,31991}, {18710,32003}, {18799,32020}, {18888,32043}, {18975,32070}, + {19060,32103}, {19143,32141}, {19224,32183}, {19303,32230}, {19378,32281}, {19450,32337}, + {19519,32397}, {19585,32460}, {19646,32528}, {19704,32599}, {19757,32673}, {19806,32750}, + {19851,32830}, {19891,32912}, {19926,32996}, {19938,33031}, {21586,33031}, {21619,32945}, + {21657,32862}, {21699,32781}, {21747,32703}, {21798,32628}, {21854,32556}, {21914,32487}, + {21978,32422}, {22046,32360}, {22117,32303}, {22191,32250}, {22268,32201}, {22348,32157}, + {22430,32117}, {22515,32083}, {22601,32053}, {22689,32028}, {22778,32009}, {22869,31995}, + {22959,31986}, {23051,31982}, {23142,31984}, {23233,31991}, {23324,32003}, {23413,32020}, + {23502,32043}, {23589,32070}, {23674,32103}, {23757,32141}, {23838,32183}, {23916,32230}, + {23992,32281}, {24064,32337}, {24133,32397}, {24199,32460}, {24260,32528}, {24318,32599}, + {24371,32673}, {24420,32750}, {24465,32830}, {24504,32912}, {24539,32996}, {24552,33031}, + {26200,33031}, {26233,32945}, {26271,32862}, {26313,32781}, {26360,32703}, {26412,32628}, + {26468,32556}, {26528,32487}, {26592,32422}, {26659,32360}, {26730,32303}, {26805,32250}, + {26882,32201}, {26962,32157}, {27044,32117}, {27129,32083}, {27215,32053}, {27303,32028}, + {27392,32009}, {27482,31995}, {27573,31986}, {27664,31982}, {27756,31984}, {27847,31991}, + {27937,32003}, {28027,32020}, {28115,32043}, {28202,32070}, {28288,32103}, {28371,32141}, + {28452,32183}, {28530,32230}, {28606,32281}, {28678,32337}, {28747,32397}, {28812,32460}, + {28874,32528}, {28932,32599}, {28985,32673}, {29034,32750}, {29078,32830}, {29118,32912}, + {29153,32996}, {29166,33031}, {30814,33031}, {30847,32945}, {30884,32862}, {30927,32781}, + {30974,32703}, {31026,32628}, {31082,32556}, {31142,32487}, {31206,32422}, {31273,32360}, + {31344,32303}, {31418,32250}, {31496,32201}, {31576,32157}, {31658,32117}, {31742,32083}, + {31829,32053}, {31917,32028}, {32006,32009}, {32096,31995}, {32187,31986}, {32278,31982}, + {32370,31984}, {32461,31991}, {32551,32003}, {32641,32020}, {32729,32043}, {32816,32070}, + {32902,32103}, {32985,32141}, {33066,32183}, {33144,32230}, {33219,32281}, {33292,32337}, + {33361,32397}, {33426,32460}, {33488,32528}, {33545,32599}, {33599,32673}, {33648,32750}, + {33692,32830}, {33732,32912}, {33767,32996}, {33779,33031}, {35427,33031}, {35460,32946}, + {35498,32863}, {35540,32782}, {35587,32704}, {35639,32629}, {35694,32557}, {35754,32489}, + {35818,32423}, {35885,32362}, {35956,32305}, {36029,32252}, {36106,32203}, {36186,32159}, + {36268,32119}, {36352,32084}, {36386,32072}, {36386,30843}, {36301,30810}, {36218,30773}, + {36137,30730}, {36059,30683}, {35983,30631}, {35911,30575}, {35842,30515}, {35777,30451}, + {35716,30384}, {35658,30313}, {35605,30239}, {35557,30161}, {35512,30081}, {35473,29999}, + {35438,29915}, {35409,29828}, {35384,29740}, {35364,29651}, {35350,29561}, {35341,29470}, + {35338,29379}, {35339,29287}, {35346,29196}, {35358,29106}, {35376,29016}, {35398,28928}, + {35426,28841}, {35458,28755}, {35496,28672}, {35538,28591}, {35585,28513}, {35637,28438}, + {35692,28365}, {35752,28296}, {35816,28231}, {35883,28169}, {35954,28112}, {36028,28058}, + {36105,28009}, {36185,27965}, {36267,27925}, {36352,27890}, {36386,27878}, {36386,26649}, + {36301,26616}, {36218,26578}, {36137,26536}, {36059,26489}, {35983,26437}, {35911,26381}, + {35842,26321}, {35777,26257}, {35716,26189}, {35658,26118}, {35605,26044}, {35557,25967}, + {35512,25887}, {35473,25805}, {35438,25720}, {35409,25634}, {35384,25546}, {35364,25457}, + {35350,25366}, {35341,25276}, {35338,25184}, {35339,25093}, {35346,25002}, {35358,24912}, + {35376,24822}, {35398,24733}, {35426,24646}, {35458,24561}, {35496,24478}, {35538,24397}, + {35585,24319}, {35637,24243}, {35692,24171}, {35752,24102}, {35816,24036}, {35883,23975}, + {35954,23917}, {36028,23864}, {36105,23815}, {36185,23770}, {36267,23731}, {36352,23696}, + {36386,23683}, {36386,22455}, {36301,22422}, {36218,22384}, {36137,22341}, {36059,22294}, + {35983,22243}, {35911,22187}, {35842,22127}, {35777,22063}, {35716,21995}, {35658,21924}, + {35605,21850}, {35557,21773}, {35512,21693}, {35473,21610}, {35438,21526}, {35409,21440}, + {35384,21352}, {35364,21262}, {35350,21172}, {35341,21081}, {35338,20990}, {35339,20899}, + {35346,20808}, {35358,20717}, {35376,20628}, {35398,20539}, {35426,20452}, {35458,20367}, + {35496,20284}, {35538,20203}, {35585,20124}, {35637,20049}, {35692,19976}, {35752,19907}, + {35816,19842}, {35883,19780}, {35954,19723}, {36028,19669}, {36105,19620}, {36185,19576}, + {36267,19536}, {36352,19501}, {36386,19489}, {36386,18260}, {36301,18227}, {36218,18190}, + {36137,18147}, {36059,18100}, {35983,18048}, {35911,17992}, {35842,17932}, {35777,17868}, + {35716,17801}, {35658,17730}, {35605,17655}, {35557,17578}, {35512,17498}, {35473,17416}, + {35438,17332}, {35409,17245}, {35384,17157}, {35364,17068}, {35350,16978}, {35341,16887}, + {35338,16796}, {35339,16704}, {35346,16613}, {35358,16523}, {35376,16433}, {35398,16345}, + {35426,16258}, {35458,16172}, {35496,16089}, {35538,16008}, {35585,15930}, {35637,15854}, + {35692,15782}, {35752,15713}, {35816,15648}, {35883,15586}, {35954,15529}, {36028,15475}, + {36105,15426}, {36185,15382}, {36267,15342}, {36352,15307}, {36386,15295}, {36386,14066}, + {36301,14033}, {36218,13995}, {36137,13953}, {36059,13906}, {35983,13854}, {35911,13798}, + {35842,13738}, {35777,13674}, {35716,13606}, {35658,13535}, {35605,13461}, {35557,13384}, + {35512,13304}, {35473,13222}, {35438,13137}, {35409,13051}, {35384,12963}, {35364,12874}, + {35350,12783}, {35341,12693}, {35338,12601}, {35339,12510}, {35346,12419}, {35358,12328}, + {35376,12239}, {35398,12150}, {35426,12063}, {35458,11978}, {35496,11895}, {35538,11814}, + {35585,11736}, {35637,11660}, {35692,11588}, {35752,11519}, {35816,11453}, {35883,11392}, + {35954,11334}, {36028,11281}, {36105,11232}, {36185,11187}, {36267,11148}, {36352,11113}, + {36386,11100}, {36386,9872}, {36301,9839}, {36218,9801}, {36137,9758}, {36059,9711}, + {35983,9660}, {35911,9604}, {35842,9544}, {35777,9480}, {35716,9412}, {35658,9341}, + {35605,9267}, {35557,9190}, {35512,9110}, {35473,9027}, {35438,8943}, {35409,8856}, + {35384,8769}, {35364,8679}, {35350,8589}, {35341,8498}, {35338,8407}, {35339,8316}, + {35346,8225}, {35358,8134}, {35376,8045}, {35398,7956}, {35426,7869}, {35458,7784}, + {35496,7700}, {35538,7620}, {35585,7541}, {35637,7466}, {35692,7393}, {35752,7324}, + {35816,7259}, {35883,7197}, {35954,7140}, {36028,7086}, {36105,7037}, {36185,6993}, + {36267,6953}, {36352,6918}, {36386,6906}, {36386,5677}, {36301,5644}, {36218,5607}, + {36137,5564}, {36059,5517}, {35983,5465}, {35911,5409}, {35842,5349}, {35777,5285}, + {35716,5218}, {35658,5147}, {35605,5072}, {35557,4995}, {35512,4915}, {35473,4833}, + {35438,4748}, {35409,4662}, {35384,4574}, {35364,4485}, {35350,4395}, {35341,4304}, + {35338,4213}, {35339,4121}, {35346,4030}, {35358,3940}, {35376,3850}, {35398,3762}, + {35426,3675}, {35458,3589}, {35496,3506}, {35538,3425}, {35585,3347}, {35637,3271}, + {35692,3199}, {35752,3130}, {35816,3065}, {35883,3003}, {35954,2945}, {36028,2892}, + {36105,2843}, {36185,2799}, {36267,2759}, {36352,2724}, {36386,2711}, {36386,1483}, + {36301,1450}, {36218,1413}, {36138,1370}, {36060,1323}, {35985,1272}, {35913,1216}, + {35844,1156}, {35779,1093}, {35718,1026}, {35660,955}, {35607,881}, {35558,804}, + {35514,725}, {35475,643}, {35440,559}, {35427,524}, {33779,524}, {33747,610}, + {33709,693}, {33666,773}, {33619,852}, {33567,927}, {33511,999}, {33451,1068}, + {33387,1133}, {33320,1195}, {33249,1252}, {33175,1305}, {33097,1354}, {33017,1398}, + {32935,1437}, {32851,1472}, {32764,1502}, {32676,1526}, {32587,1546}, {32497,1560}, + {32406,1569}, {32315,1573}, {32223,1571}, {32132,1564}, {32042,1552}, {31952,1535}, + {31864,1512}, {31777,1485}, {31691,1452}, {31608,1414}, {31527,1372}, {31449,1325}, + {31374,1274}, {31301,1218}, {31232,1158}, {31167,1094}, {31105,1027}, {31048,956}, + {30994,882}, {30945,805}, {30901,725}, {30861,643}, {30826,559}, {30814,524}, + {29166,524}, {29133,610}, {29095,693}, {29052,773}, {29005,852}, {28954,927}, + {28898,999}, {28838,1068}, {28774,1133}, {28706,1195}, {28635,1252}, {28561,1305}, + {28484,1354}, {28404,1398}, {28321,1437}, {28237,1472}, {28150,1502}, {28063,1526}, + {27973,1546}, {27883,1560}, {27792,1569}, {27701,1573}, {27610,1571}, {27519,1564}, + {27428,1552}, {27338,1535}, {27250,1512}, {27163,1485}, {27078,1452}, {26994,1414}, + {26914,1372}, {26835,1325}, {26760,1274}, {26687,1218}, {26618,1158}, {26553,1094}, + {26491,1027}, {26434,956}, {26380,882}, {26331,805}, {26287,725}, {26247,643}, + {26212,559}, {26200,524}, {24552,524}, {24519,610}, {24481,693}, {24439,773}, + {24391,852}, {24340,927}, {24284,999}, {24224,1068}, {24160,1133}, {24092,1195}, + {24021,1252}, {23947,1305}, {23870,1354}, {23790,1398}, {23708,1437}, {23623,1472}, + {23537,1502}, {23449,1526}, {23360,1546}, {23269,1560}, {23178,1569}, {23087,1573}, + {22996,1571}, {22905,1564}, {22814,1552}, {22725,1535}, {22636,1512}, {22549,1485}, + {22464,1452}, {22381,1414}, {22300,1372}, {22221,1325}, {22146,1274}, {22074,1218}, + {22005,1158}, {21939,1094}, {21878,1027}, {21820,956}, {21767,882}, {21718,805}, + {21673,725}, {21633,643}, {21599,559}, {21586,524}, {19938,524}, {19905,610}, + {19867,693}, {19825,773}, {19778,852}, {19726,927}, {19670,999}, {19610,1068}, + {19546,1133}, {19478,1195}, {19407,1252}, {19333,1305}, {19256,1354}, {19176,1398}, + {19094,1437}, {19009,1472}, {18923,1502}, {18835,1526}, {18746,1546}, {18656,1560}, + {18565,1569}, {18473,1573}, {18382,1571}, {18291,1564}, {18201,1552}, {18111,1535}, + {18022,1512}, {17935,1485}, {17850,1452}, {17767,1414}, {17686,1372}, {17608,1325}, + {17532,1274}, {17460,1218}, {17391,1158}, {17325,1094}, {17264,1027}, {17206,956}, + {17153,882}, {17104,805}, {17059,725}, {17020,643}, {16985,559}, {16972,524}, + {15324,524}, {15291,610}, {15254,693}, {15211,773}, {15164,852}, {15112,927}, + {15056,999}, {14996,1068}, {14932,1133}, {14865,1195}, {14794,1252}, {14719,1305}, + {14642,1354}, {14562,1398}, {14480,1437}, {14395,1472}, {14309,1502}, {14221,1526}, + {14132,1546}, {14042,1560}, {13951,1569}, {13860,1573}, {13768,1571}, {13677,1564}, + {13587,1552}, {13497,1535}, {13409,1512}, {13322,1485}, {13236,1452}, {13153,1414}, + {13072,1372}, {12994,1325}, {12918,1274}, {12846,1218}, {12777,1158}, {12712,1094}, + {12650,1027}, {12592,956}, {12539,882}, {12490,805}, {12446,725}, {12406,643}, + {12371,559}, {12358,524}, {10711,524}, {10678,610}, {10640,693}, {10597,773}, + {10550,852}, {10498,927}, {10442,999}, {10382,1068}, {10319,1133}, {10251,1195}, + {10180,1252}, {10106,1305}, {10028,1354}, {9949,1398}, {9866,1437}, {9782,1472}, + {9695,1502}, {9607,1526}, {9518,1546}, {9428,1560}, {9337,1569}, {9246,1573}, + {9155,1571}, {9064,1564}, {8973,1552}, {8883,1535}, {8795,1512}, {8708,1485}, + {8623,1452}, {8539,1414}, {8458,1372}, {8380,1325}, {8305,1274}, {8232,1218}, + {8163,1158}, {8098,1094}, {8036,1027}, {7979,956}, {7925,882}, {7876,805}, + {7832,725}, {7792,643}, {7757,559}, {7745,524}, {6097,524}, {6064,610}, + {6026,693}, {5983,773}, {5936,852}, {5885,927}, {5829,999}, {5769,1068}, + {5705,1133}, {5637,1195}, {5566,1252}, {5492,1305}, {5415,1354}, {5335,1398}, + {5252,1437}, {5168,1472}, {5082,1502}, {4994,1526}, {4904,1546}, {4814,1560}, + {4723,1569}, {4632,1573}, {4541,1571}, {4450,1564}, {4359,1552}, {4270,1535}, + {4181,1512}, {4094,1485}, {4009,1452}, {3926,1414}, {3845,1372}, {3766,1325}, + {3691,1274}, {3619,1218}, {3550,1158}, {3484,1094}, {3423,1027}, {3365,956}, + {3312,882}, {3263,805}, {3218,725}, {3178,643}, {3143,559}, {3131,524}, + {1483,524}, {1450,609}, {1413,692}, {1370,773}, {1323,851}, {1272,926}, + {1216,998}, {1156,1066}, {1093,1131}, {1026,1193}, {955,1250}, {881,1303}, + {804,1352}, {725,1396}, {643,1436}, {559,1470}}, + + {{-47877,-47877}, {84788,-47877}, {84788,81432}, {-47877,81432}} + }; + + Paths64 solution = InflatePaths(subject, -10000, JoinType::Round, EndType::Polygon); EXPECT_EQ(solution.size(), 2); } + +TEST(Clipper2Tests, TestOffsets6) // also modified from #593 (tests rounded ends) +{ + Paths64 subjects = { + {{620,620}, {-620,620}, {-620,-620}, {620,-620}}, + + {{20,-277}, {42,-275}, {59,-272}, {80,-266}, {97,-261}, {114,-254}, + {135,-243}, {149,-235}, {167,-222}, {182,-211}, {197,-197}, + {212,-181}, {223,-167}, {234,-150}, {244,-133}, {253,-116}, + {260,-99}, {267,-78}, {272,-61}, {275,-40}, {278,-18}, {276,-39}, + {272,-61}, {267,-79}, {260,-99}, {253,-116}, {245,-133}, {235,-150}, + {223,-167}, {212,-181}, {197,-197}, {182,-211}, {168,-222}, {152,-233}, + {135,-243}, {114,-254}, {97,-261}, {80,-267}, {59,-272}, {42,-275}, {20,-278}} + }; + + const double offset = -50; + Clipper2Lib::ClipperOffset offseter; + Clipper2Lib::Paths64 tmpSubject; + + offseter.AddPaths(subjects, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); + Clipper2Lib::Paths64 solution; + offseter.Execute(offset, solution); + + EXPECT_EQ(solution.size(), 2); + + double area = Area(solution[1]); + EXPECT_LT(area, -47500); +} diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index d1ce9994..3dea65c4 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 September 2023 * +* Date : 19 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -417,19 +417,16 @@ private void DoRound(Group group, Path64 path, int j, int k, double angle) #else group.outPath.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y)); #endif - if (angle > -Math.PI + 0.01) // avoid 180deg concave + int steps = (int) Math.Ceiling(_stepsPerRad * Math.Abs(angle)); + for (int i = 1; i < steps; i++) // ie 1 less than steps { - int steps = (int) Math.Ceiling(_stepsPerRad * Math.Abs(angle)); - for (int i = 1; i < steps; i++) // ie 1 less than steps - { - offsetVec = new PointD(offsetVec.x * _stepCos - _stepSin * offsetVec.y, - offsetVec.x * _stepSin + offsetVec.y * _stepCos); + offsetVec = new PointD(offsetVec.x * _stepCos - _stepSin * offsetVec.y, + offsetVec.x * _stepSin + offsetVec.y * _stepCos); #if USINGZ - group.outPath.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y, pt.Z)); + group.outPath.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y, pt.Z)); #else - group.outPath.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y)); + group.outPath.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y)); #endif - } } group.outPath.Add(GetPerpendic(pt, _normals[j])); } From 0b4da7eb22f3f0b8320792ad8117adf9e9abc0d6 Mon Sep 17 00:00:00 2001 From: angusj Date: Tue, 19 Sep 2023 06:59:25 +1000 Subject: [PATCH 40/95] Removed redundant GetIntersectPt function //#568 --- CSharp/Clipper2Lib/Clipper.Core.cs | 28 ++++------------------------ CSharp/Clipper2Lib/Clipper.Engine.cs | 4 ++-- 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/CSharp/Clipper2Lib/Clipper.Core.cs b/CSharp/Clipper2Lib/Clipper.Core.cs index dbea7ef2..e5818594 100644 --- a/CSharp/Clipper2Lib/Clipper.Core.cs +++ b/CSharp/Clipper2Lib/Clipper.Core.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 July 2023 * +* Date : 19 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core structures and functions for the Clipper Library * @@ -627,8 +627,9 @@ internal static long CheckCastInt64(double val) return (long)Math.Round(val); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool GetIntersectPt(Point64 ln1a, + public static bool GetIntersectPoint(Point64 ln1a, Point64 ln1b, Point64 ln2a, Point64 ln2b, out Point64 ip) { double dy1 = (ln1b.Y - ln1a.Y); @@ -643,33 +644,12 @@ internal static bool GetIntersectPt(Point64 ln1a, } double t = ((ln1a.X - ln2a.X) * dy2 - (ln1a.Y - ln2a.Y) * dx2) / det; - if (t <= 0.0) ip = ln1a; + if (t <= 0.0) ip = ln1a; else if (t >= 1.0) ip = ln1b; else ip = new Point64(ln1a.X + t * dx1, ln1a.Y + t * dy1); return true; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool GetIntersectPoint(Point64 ln1a, - Point64 ln1b, Point64 ln2a, Point64 ln2b, out Point64 ip) - { - double dy1 = (ln1b.Y - ln1a.Y); - double dx1 = (ln1b.X - ln1a.X); - double dy2 = (ln2b.Y - ln2a.Y); - double dx2 = (ln2b.X - ln2a.X); - double det = dy1 * dx2 - dy2 * dx1; - if (det == 0.0) - { - ip = new Point64(); - return false; - } - double t = ((ln1a.X - ln2a.X) * dy2 - (ln1a.Y - ln2a.Y) * dx2) / det; - if (t <= 0.0) ip = ln1a; // ?? check further (see also #568) - else if (t >= 1.0) ip = ln2a; // ?? check further - else ip = new Point64 (ln1a.X + t * dx1, ln1a.Y + t * dy1); - return true; - } - internal static bool SegsIntersect(Point64 seg1a, Point64 seg1b, Point64 seg2a, Point64 seg2b, bool inclusive = false) { diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 31e6b016..ecb3c8d4 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 3 September 2023 * +* Date : 19 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -1899,7 +1899,7 @@ private void DisposeIntersectNodes() [MethodImpl(MethodImplOptions.AggressiveInlining)] private void AddNewIntersectNode(Active ae1, Active ae2, long topY) { - if (!InternalClipper.GetIntersectPt( + if (!InternalClipper.GetIntersectPoint( ae1.bot, ae1.top, ae2.bot, ae2.top, out Point64 ip)) ip = new Point64(ae1.curX, topY); From 09835045de0d5231648271f379a9624a6ddea96d Mon Sep 17 00:00:00 2001 From: SebastianDirks <68428517+SebastianDirks@users.noreply.github.com> Date: Tue, 19 Sep 2023 01:38:01 +0200 Subject: [PATCH 41/95] When testing if a group is smaller than the offset (and thus will vanish in the output), not only test the bounding box width but also the height. (#651) If either dimension is smaller than 2 times offset, the group will vanish when contracting. Suspected to fix issue #593. All tests passed locally in C# and C++. Delphi is missing yet, I am not familiar with that language. --- CPP/Clipper2Lib/src/clipper.offset.cpp | 3 ++- CSharp/Clipper2Lib/Clipper.Offset.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index 8aa8c58f..2c63284a 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -358,7 +358,8 @@ void ClipperOffset::OffsetPolygon(Group& group, Path64& path) if ((a < 0) != (group_delta_ < 0)) { Rect64 rec = GetBounds(path); - if (std::fabs(group_delta_) * 2 > rec.Width()) return; + double offsetMinDim = std::fabs(group_delta_) * 2; + if (offsetMinDim > rec.Width() || offsetMinDim > rec.Height()) return; } for (Path64::size_type j = 0, k = path.size() -1; j < path.size(); k = j, ++j) diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index 3dea65c4..8d4a9c90 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -502,7 +502,8 @@ private void OffsetPolygon(Group group, Path64 path) if ((a < 0) != (_groupDelta < 0)) { Rect64 rec = Clipper.GetBounds(path); - if (Math.Abs(_groupDelta) * 2 > rec.Width) return; + double offsetMinDim = Math.Abs(_groupDelta) * 2; + if (offsetMinDim > rec.Width || offsetMinDim > rec.Height) return; } group.outPath = new Path64(); From df61525985a836ef9b900782293afac2e932a188 Mon Sep 17 00:00:00 2001 From: angusj Date: Tue, 19 Sep 2023 09:44:07 +1000 Subject: [PATCH 42/95] Improved testing for excessive offset shrinking //#651 --- Delphi/Clipper2Lib/Clipper.Offset.pas | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index 69fa73db..5ea37644 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 16 September 2023 * +* Date : 19 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -430,7 +430,7 @@ procedure TClipperOffset.BuildNormals; procedure TClipperOffset.OffsetPolygon; var i,j: integer; - a: double; + a, offsetMinDim: double; rec: TRect64; begin //when the path is contracting, make sure @@ -440,7 +440,8 @@ procedure TClipperOffset.OffsetPolygon; if (a < 0) <> (fGroupDelta < 0) then begin rec := GetBounds(fInPath); - if Abs(fGroupDelta) * 2 >= rec.Width then Exit; + offsetMinDim := Abs(fGroupDelta) * 2; + if (offsetMinDim >= rec.Width) or (offsetMinDim >= rec.Height) then Exit; end; j := high(fInPath); From f9a40f05f8a044a5fbef7e280630f9a80dfc79b2 Mon Sep 17 00:00:00 2001 From: Daniel Lutz <26437097+lutz@users.noreply.github.com> Date: Tue, 19 Sep 2023 01:48:30 +0200 Subject: [PATCH 43/95] create snk for signing (#647) --- CSharp/Clipper2Lib/Clipper2.snk | Bin 0 -> 596 bytes CSharp/Clipper2Lib/Clipper2Lib.csproj | 59 ++++++++++++++------------ 2 files changed, 31 insertions(+), 28 deletions(-) create mode 100644 CSharp/Clipper2Lib/Clipper2.snk diff --git a/CSharp/Clipper2Lib/Clipper2.snk b/CSharp/Clipper2Lib/Clipper2.snk new file mode 100644 index 0000000000000000000000000000000000000000..830c886335fae38762ab97f58ea19b57784e7a95 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50098`;VVe~>%Zk)p!lEZF-t)y5`-Q64~NWN z>(!+g-=5Lob!^5+3SjKE@6MFYz# z%lETl;Lnv5C|$~kIjW!<%dj7|Noc79gYS$noZR73fAtd+a~pz1uE1aBhk0MqE+O*p|! z=9wb*e&k;YEht8p&N8Rmgw&y)i_!fXW0|&K?>!8#keu%uL)rtLnj}J2s(WJRSfitZ zAtq^|ZV?>qX$gq+D#lT9)-i&s34rCAh!EtcKePX4v)&}110WOHg*uws*(2!w>9RXa zA6I3VD`d@4xMXHS&LssCS_Vf+uS@-)#`n792N>CS8d%C{l#Wyw_kyRWWrR>1uPu1R@%W*me6u@x zie(8ZtXy%p-F(n6OshH-SD2B#oNR&$=_==ZyR_isAzG8sTcCNhxsv literal 0 HcmV?d00001 diff --git a/CSharp/Clipper2Lib/Clipper2Lib.csproj b/CSharp/Clipper2Lib/Clipper2Lib.csproj index 93e544b2..8e75b825 100644 --- a/CSharp/Clipper2Lib/Clipper2Lib.csproj +++ b/CSharp/Clipper2Lib/Clipper2Lib.csproj @@ -1,28 +1,31 @@ - - - - netstandard2.1 - enable - 8 - True - Angus Johnson - Polygon Clipping and Offsetting Library - Clipper2 - Clipper2 - http://www.angusj.com/clipper2/Docs/Overview.htm - Copyright © 2010-2022 - git - https://github.com/AngusJohnson/Clipper2 - Major revision to merging polygons in clipping solutions - AnyCPU;x86 - - - - TRACE - - - - TRACE - - - + + + + netstandard2.1 + enable + 8 + True + Angus Johnson + Polygon Clipping and Offsetting Library + Clipper2 + Clipper2 + http://www.angusj.com/clipper2/Docs/Overview.htm + Copyright © 2010-2023 + git + https://github.com/AngusJohnson/Clipper2 + Major revision to merging polygons in clipping solutions + AnyCPU;x86 + true + Clipper2.snk + false + + + + TRACE + + + + TRACE + + + From b444ed389d2996e278e2cb1eb055ce6cb464b5e9 Mon Sep 17 00:00:00 2001 From: Juha Reunanen Date: Sat, 23 Sep 2023 00:33:34 +0300 Subject: [PATCH 44/95] Improve reporting test failures: separately show the measured and the stored values (#658) --- CPP/Tests/TestPolygons.cpp | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/CPP/Tests/TestPolygons.cpp b/CPP/Tests/TestPolygons.cpp index 1b30999c..91746065 100644 --- a/CPP/Tests/TestPolygons.cpp +++ b/CPP/Tests/TestPolygons.cpp @@ -52,9 +52,6 @@ TEST(Clipper2Tests, TestMultiplePolygons) const int64_t measured_area = static_cast(Area(solution)); const int64_t measured_count = static_cast(solution.size() + solution_open.size()); - const int64_t count_diff = stored_count <= 0 ? 0 : std::abs(measured_count - stored_count); - const int64_t area_diff = stored_area <= 0 ? 0 : std::abs(measured_area - stored_area); - double area_diff_ratio = (area_diff == 0) ? 0 : std::fabs((double)(area_diff) / measured_area); // check the polytree variant too Clipper2Lib::PolyTree64 solution_polytree; @@ -76,33 +73,33 @@ TEST(Clipper2Tests, TestMultiplePolygons) ; // skip count else if (IsInList(test_number, { 120, 121, 130, 138, 140, 148, 163, 165, 166, 167, 168, 172, 175, 178, 180 })) - EXPECT_LE(count_diff, 5) << " in test " << test_number; + EXPECT_NEAR(measured_count, stored_count, 5) << " in test " << test_number; else if (IsInList(test_number, { 27, 181 })) - EXPECT_LE(count_diff, 2) << " in test " << test_number; + EXPECT_NEAR(measured_count, stored_count, 2) << " in test " << test_number; else if (test_number >= 120 && test_number <= 184) - EXPECT_LE(count_diff, 2) << " in test " << test_number; + EXPECT_NEAR(measured_count, stored_count, 2) << " in test " << test_number; else if (IsInList(test_number, { 23, 45, 87, 102, 111, 113, 191 })) - EXPECT_LE(count_diff, 1) << " in test " << test_number; + EXPECT_NEAR(measured_count, stored_count, 1) << " in test " << test_number; else - EXPECT_EQ(count_diff, 0) << " in test " << test_number; + EXPECT_EQ(measured_count, stored_count) << " in test " << test_number; // check polygon areas if (stored_area <= 0) ; // skip area else if (IsInList(test_number, { 19, 22, 23, 24 })) - EXPECT_LE((double)area_diff_ratio, 0.5) << " in test " << test_number; + EXPECT_NEAR(measured_area, stored_area, 0.5 * measured_area) << " in test " << test_number; else if (test_number == 193) - EXPECT_LE((double)area_diff_ratio, 0.2) << " in test " << test_number; + EXPECT_NEAR(measured_area, stored_area, 0.2 * measured_area) << " in test " << test_number; else if (test_number == 63) - EXPECT_LE((double)area_diff_ratio, 0.1) << " in test " << test_number; + EXPECT_NEAR(measured_area, stored_area, 0.1 * measured_area) << " in test " << test_number; else if (test_number == 16) - EXPECT_LE((double)area_diff_ratio, 0.075) << " in test " << test_number; + EXPECT_NEAR(measured_area, stored_area, 0.075 * measured_area) << " in test " << test_number; else if (test_number == 26) - EXPECT_LE((double)area_diff_ratio, 0.05) << " in test " << test_number; + EXPECT_NEAR(measured_area, stored_area, 0.05 * measured_area) << " in test " << test_number; else if (IsInList(test_number, { 15, 52, 53, 54, 59, 60, 64, 117, 119, 184 })) - EXPECT_LE((double)area_diff_ratio, 0.02) << " in test " << test_number; + EXPECT_NEAR(measured_area, stored_area, 0.02 * measured_area) << " in test " << test_number; else - EXPECT_LE((double)area_diff_ratio, 0.01) << " in test " << test_number; + EXPECT_NEAR(measured_area, stored_area, 0.01 * measured_area) << " in test " << test_number; EXPECT_EQ(measured_count, measured_count_polytree) << " in test " << test_number; From b462b5bf645338d25a0759b0188e5e37fe511942 Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 24 Sep 2023 19:42:21 +1000 Subject: [PATCH 45/95] Added a new join type (JoinType::Bevel) for offsetting //#615 --- .../include/clipper2/clipper.offset.h | 9 ++- CPP/Clipper2Lib/src/clipper.offset.cpp | 53 +++++++------- CPP/Examples/Inflate/Inflate.cpp | 28 ++++--- CPP/Utils/clipper.svg.cpp | 2 +- .../Clipper2Lib.Examples/InflateDemo/Main.cs | 27 +++++-- CSharp/Clipper2Lib/Clipper.Offset.cs | 60 +++++++-------- CSharp/Utils/SVG/Clipper.SVG.cs | 2 +- Delphi/Clipper2Lib/Clipper.Offset.pas | 73 ++++++++++--------- Delphi/Examples/Example1/Example1.dpr | 33 +++++++++ 9 files changed, 174 insertions(+), 113 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.offset.h b/CPP/Clipper2Lib/include/clipper2/clipper.offset.h index 8835fb0f..e48ae9dc 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.offset.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.offset.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 15 May 2023 * +* Date : 24 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -15,7 +15,9 @@ namespace Clipper2Lib { -enum class JoinType { Square, Round, Miter }; +enum class JoinType { Square, Bevel, Round, Miter }; +//Square : Joins are 'squared' at exactly the offset distance (more complex code) +//Bevel : Similar to Square, but the offset distance varies with angle (simple code & faster) enum class EndType {Polygon, Joined, Butt, Square, Round}; //Butt : offsets both sides of a path, with square blunt ends @@ -51,7 +53,7 @@ class ClipperOffset { PathD norms; Paths64 solution; std::vector groups_; - JoinType join_type_ = JoinType::Square; + JoinType join_type_ = JoinType::Bevel; EndType end_type_ = EndType::Polygon; double miter_limit_ = 0.0; @@ -64,6 +66,7 @@ class ClipperOffset { #endif DeltaCallback64 deltaCallback64_ = nullptr; + void DoBevel(Group& group, const Path64& path, size_t j, size_t k); void DoSquare(Group& group, const Path64& path, size_t j, size_t k); void DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a); void DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle); diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index 2c63284a..0439ec52 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 19 September 2023 * +* Date : 24 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -200,6 +200,24 @@ PointD IntersectPoint(const PointD& pt1a, const PointD& pt1b, } } +void ClipperOffset::DoBevel(Group& group, const Path64& path, size_t j, size_t k) +{ + PointD pt1, pt2; + if (j == k) + { + double abs_delta = std::abs(group_delta_); + pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y); + pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y); + } + else + { + pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y); + pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y); + } + group.path.push_back(Point64(pt1)); + group.path.push_back(Point64(pt2)); +} + void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t k) { PointD vec; @@ -342,10 +360,13 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a); else DoSquare(group, path, j, k); } - else if (cos_a > 0.99 || join_type_ == JoinType::Square) // 0.99 ~= 8.1 deg. - DoSquare(group, path, j, k); - else + else if (cos_a > 0.99 || join_type_ == JoinType::Bevel) + // ie > 2.5 deg (see above) but less than ~8 deg ( acos(0.99) ) + DoBevel(group, path, j, k); + else if (join_type_ == JoinType::Round) DoRound(group, path, j, k, std::atan2(sin_a, cos_a)); + else + DoSquare(group, path, j, k); } void ClipperOffset::OffsetPolygon(Group& group, Path64& path) @@ -394,17 +415,7 @@ void ClipperOffset::OffsetOpenPath(Group& group, Path64& path) switch (end_type_) { case EndType::Butt: -#ifdef USINGZ - group.path.push_back(Point64( - path[0].x - norms[0].x * group_delta_, - path[0].y - norms[0].y * group_delta_, - path[0].z)); -#else - group.path.push_back(Point64( - path[0].x - norms[0].x * group_delta_, - path[0].y - norms[0].y * group_delta_)); -#endif - group.path.push_back(GetPerpendic(path[0], norms[0], group_delta_)); + DoBevel(group, path, 0, 0); break; case EndType::Round: DoRound(group, path, 0, 0, PI); @@ -436,17 +447,7 @@ void ClipperOffset::OffsetOpenPath(Group& group, Path64& path) switch (end_type_) { case EndType::Butt: -#ifdef USINGZ - group.path.push_back(Point64( - path[highI].x - norms[highI].x * group_delta_, - path[highI].y - norms[highI].y * group_delta_, - path[highI].z)); -#else - group.path.push_back(Point64( - path[highI].x - norms[highI].x * group_delta_, - path[highI].y - norms[highI].y * group_delta_)); -#endif - group.path.push_back(GetPerpendic(path[highI], norms[highI], group_delta_)); + DoBevel(group, path, highI, highI); break; case EndType::Round: DoRound(group, path, highI, highI, PI); diff --git a/CPP/Examples/Inflate/Inflate.cpp b/CPP/Examples/Inflate/Inflate.cpp index 32e4f21e..cc181169 100644 --- a/CPP/Examples/Inflate/Inflate.cpp +++ b/CPP/Examples/Inflate/Inflate.cpp @@ -13,10 +13,10 @@ void System(const std::string& filename); int main(int argc, char* argv[]) { - //DoSimpleShapes(); + DoSimpleShapes(); DoRabbit(); - std::getchar(); + //std::getchar(); } void DoSimpleShapes() @@ -26,23 +26,29 @@ void DoSimpleShapes() FillRule fr2 = FillRule::EvenOdd; SvgWriter svg2; - op1.push_back(MakePath({ 80,60, 20,20, 180,20, 180,80, 20,180, 180,180 })); - op2 = InflatePaths(op1, 20, JoinType::Square, EndType::Butt); + op1.push_back(MakePath({ 80,60, 20,20, 180,20, 180,70, 25,150, 20,180, 180,180 })); + op2 = InflatePaths(op1, 20, JoinType::Miter, EndType::Square, 3); SvgAddOpenSubject(svg2, op1, fr2, false); SvgAddSolution(svg2, Paths64ToPathsD(op2), fr2, false); - SvgAddCaption(svg2, "Square Joins; Butt Ends", 20, 220); + SvgAddCaption(svg2, "Miter Joins; Square Ends", 20, 210); - op1 = TranslatePaths(op1, 250, 0); - op2 = InflatePaths(op1, 20, JoinType::Miter, EndType::Square, 3); + op1 = TranslatePaths(op1, 210, 0); + op2 = InflatePaths(op1, 20, JoinType::Square, EndType::Square); + SvgAddOpenSubject(svg2, op1, fr2, false); + SvgAddSolution(svg2, Paths64ToPathsD(op2), fr2, false); + SvgAddCaption(svg2, "Square Joins; Square Ends", 230, 210); + + op1 = TranslatePaths(op1, 210, 0); + op2 = InflatePaths(op1, 20, JoinType::Bevel, EndType::Butt, 3); SvgAddOpenSubject(svg2, op1, fr2, false); SvgAddSolution(svg2, Paths64ToPathsD(op2), fr2, false); - SvgAddCaption(svg2, "Miter Joins; Square Ends", 300, 220); + SvgAddCaption(svg2, "Bevel Joins; Butt Ends", 440, 210); - op1 = TranslatePaths(op1, 250, 0); + op1 = TranslatePaths(op1, 210, 0); op2 = InflatePaths(op1, 20, JoinType::Round, EndType::Round); SvgAddOpenSubject(svg2, op1, fr2, false); SvgAddSolution(svg2, Paths64ToPathsD(op2), fr2, false); - SvgAddCaption(svg2, "Round Joins; Round Ends", 580, 220); + SvgAddCaption(svg2, "Round Joins; Round Ends", 650, 210); SvgSaveToFile(svg2, "open_paths.svg", 800, 600, 20); System("open_paths.svg"); @@ -68,7 +74,7 @@ void DoSimpleShapes() //different join types within the same offset operation ClipperOffset co; co.AddPaths(p, JoinType::Miter, EndType::Joined); - p = TranslatePaths(p, 120, 100); + p = TranslatePaths(p, 120, 100); pp.insert(pp.end(), p.begin(), p.end()); co.AddPaths(p, JoinType::Round, EndType::Joined); co.Execute(20, p); diff --git a/CPP/Utils/clipper.svg.cpp b/CPP/Utils/clipper.svg.cpp index c7b1894e..337a07ab 100644 --- a/CPP/Utils/clipper.svg.cpp +++ b/CPP/Utils/clipper.svg.cpp @@ -260,7 +260,7 @@ namespace Clipper2Lib { file << " font_name << "\" font-size=\"" << ti->font_size << "\" fill=\"" << ColorToHtml(ti->font_color) << "\" fill-opacity=\"" << GetAlphaAsFrac(ti->font_color) << "\">\n"; - file << " x + margin) << "\" y=\"" << (ti->y+margin) << "\">" << + file << " x * scale + offsetX) << "\" y=\"" << (ti->y * scale + offsetY) << "\">" << ti->text << "\n \n\n"; } diff --git a/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs b/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs index 31d8aa7a..f7542aa2 100644 --- a/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs +++ b/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs @@ -1,8 +1,8 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 19 July 2023 * +* Date : 24 September 2023 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2022 * +* Copyright : Angus Johnson 2010-2023 * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ @@ -32,28 +32,39 @@ public static void DoSimpleShapes() for (int i = 0; i < 5; ++i) { - //nb: the following '10' parameter greatly increases miter limit + //nb: the last parameter here (10) greatly increases miter limit p = Clipper.InflatePaths(p, 5, JoinType.Miter, EndType.Polygon, 10); pp.AddRange(p); } //rectangle offset - both squared and rounded p.Clear(); - p.Add(Clipper.MakePath(new int[] { 100, 0, 340, 0, 340, 200, 100, 200 })); - pp.AddRange(p); + ClipperOffset co = new(); + //nb: using the ClipperOffest class directly here to control //different join types within the same offset operation - ClipperOffset co = new(); + p.Add(Clipper.MakePath(new int[] { 100, 0, 340, 0, 340, 200, 100, 200 })); + pp.AddRange(p); + co.AddPaths(p, JoinType.Bevel, EndType.Joined); + + p = Clipper.TranslatePaths(p, 60, 50); + pp.AddRange(p); co.AddPaths(p, JoinType.Square, EndType.Joined); - p = Clipper.TranslatePaths(p, 120, 100); + + p = Clipper.TranslatePaths(p, 60, 50); pp.AddRange(p); co.AddPaths(p, JoinType.Round, EndType.Joined); + + co.Execute(20, p); pp.AddRange(p); string filename = "../../../inflate.svg"; SvgWriter svg = new(); SvgUtils.AddSolution(svg, pp, 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); ClipperFileIO.OpenFileWithDefaultApp(filename); } @@ -69,7 +80,7 @@ public static void DoRabbit() pd = Clipper.InflatePaths(pd, -2.5, JoinType.Round, EndType.Polygon); // SimplifyPaths - is not essential but it not only // speeds up the loop but it also tidies the result - pd = Clipper.SimplifyPaths(pd, 0.2); + pd = Clipper.SimplifyPaths(pd, 0.25); solution.AddRange(pd); } diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index 8d4a9c90..eb9eba0b 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 19 September 2023 * +* Date : 24 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -15,9 +15,10 @@ namespace Clipper2Lib { public enum JoinType { + Miter, Square, - Round, - Miter + Bevel, + Round }; public enum EndType @@ -323,6 +324,25 @@ private PointD GetPerpendicD(Point64 pt, PointD norm) #endif } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DoBevel(Group group, Path64 path, int j, int k) + { + Point64 pt1, pt2; + if (j == k) + { + double absDelta = Math.Abs(_groupDelta); + pt1 = new Point64(path[j].X - absDelta * _normals[j].x, path[j].Y - absDelta * _normals[j].y); + pt2 = new Point64(path[j].X + absDelta * _normals[j].x, path[j].Y + absDelta * _normals[j].y); + } + else + { + pt1 = new Point64(path[j].X + _groupDelta * _normals[k].x, path[j].Y + _groupDelta * _normals[k].y); + pt2 = new Point64(path[j].X + _groupDelta * _normals[j].x, path[j].Y + _groupDelta * _normals[j].y); + } + group.outPath.Add(pt1); + group.outPath.Add(pt2); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DoSquare(Group group, Path64 path, int j, int k) { @@ -483,12 +503,14 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) if (cosA > _mitLimSqr - 1) DoMiter(group, path, j, k, cosA); else DoSquare(group, path, j, k); } - else if (cosA > 0.99 || _joinType == JoinType.Square) + else if (cosA > 0.99 || _joinType == JoinType.Bevel) //angle less than 8 degrees or a squared join - DoSquare(group, path, j, k); - else + DoBevel(group, path, j, k); + else if (_joinType == JoinType.Round) DoRound(group, path, j, k, Math.Atan2(sinA, cosA)); - + else + DoSquare(group, path, j, k); + k = j; } @@ -537,17 +559,7 @@ private void OffsetOpenPath(Group group, Path64 path) switch (_endType) { case EndType.Butt: -#if USINGZ - group.outPath.Add(new Point64( - path[0].X - _normals[0].x * _groupDelta, - path[0].Y - _normals[0].y * _groupDelta, - path[0].Z)); -#else - group.outPath.Add(new Point64( - path[0].X - _normals[0].x * _groupDelta, - path[0].Y - _normals[0].y * _groupDelta)); -#endif - group.outPath.Add(GetPerpendic(path[0], _normals[0])); + DoBevel(group, path, 0, 0); break; case EndType.Round: DoRound(group, path, 0, 0, Math.PI); @@ -575,17 +587,7 @@ private void OffsetOpenPath(Group group, Path64 path) switch (_endType) { case EndType.Butt: -#if USINGZ - group.outPath.Add(new Point64( - path[highI].X - _normals[highI].x * _groupDelta, - path[highI].Y - _normals[highI].y * _groupDelta, - path[highI].Z)); -#else - group.outPath.Add(new Point64( - path[highI].X - _normals[highI].x * _groupDelta, - path[highI].Y - _normals[highI].y * _groupDelta)); -#endif - group.outPath.Add(GetPerpendic(path[highI], _normals[highI])); + DoBevel(group, path, highI, highI); break; case EndType.Round: DoRound(group, path, highI, highI, Math.PI); diff --git a/CSharp/Utils/SVG/Clipper.SVG.cs b/CSharp/Utils/SVG/Clipper.SVG.cs index 08f922fb..42ffdd5e 100644 --- a/CSharp/Utils/SVG/Clipper.SVG.cs +++ b/CSharp/Utils/SVG/Clipper.SVG.cs @@ -305,7 +305,7 @@ public bool SaveToFile(string filename, int maxWidth = 0, int maxHeight = 0, int { writer.Write("\n", captionInfo.fontSize, ColorToHtml(captionInfo.fontColor)); - writer.Write("{2}\n\n", captionInfo.posX + margin, captionInfo.posY + margin, captionInfo.text); + writer.Write("{2}\n\n", captionInfo.posX * scale + offsetX, captionInfo.posY * scale + offsetX, captionInfo.text); } writer.Write("\n"); diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index 5ea37644..f3bf669a 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 19 September 2023 * +* Date : 24 September 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -18,7 +18,10 @@ interface type - TJoinType = (jtSquare, jtRound, jtMiter); + TJoinType = (jtMiter, jtSquare, jtBevel, jtRound); + //jtSquare: Joins are 'squared' at exactly the offset distance (complex code) + //jtBevel : offset distances vary depending on the angle (simple code, faster) + TEndType = (etPolygon, etJoined, etButt, etSquare, etRound); // etButt : offsets both sides of a path, with square blunt ends // etSquare : offsets both sides of a path, with square extended ends @@ -70,6 +73,7 @@ TClipperOffset = class procedure AddPoint(const pt: TPoint64); overload; {$IFDEF INLINING} inline; {$ENDIF} procedure DoSquare(j, k: Integer); + procedure DoBevel(j, k: Integer); procedure DoMiter(j, k: Integer; cosA: Double); procedure DoRound(j, k: integer; angle: double); procedure OffsetPoint(j: Integer; var k: integer); @@ -483,20 +487,7 @@ procedure TClipperOffset.OffsetOpenPath; AddPoint(fInPath[0]); end else case fEndType of - etButt: - begin -{$IFDEF USINGZ} - with fInPath[0] do AddPoint(Point64( - X - fNorms[0].X * fGroupDelta, - Y - fNorms[0].Y * fGroupDelta, - Z)); -{$ELSE} - with fInPath[0] do AddPoint(Point64( - X - fNorms[0].X * fGroupDelta, - Y - fNorms[0].Y * fGroupDelta)); -{$ENDIF} - AddPoint(GetPerpendic(fInPath[0], fNorms[0], fGroupDelta)); - end; + etButt: DoBevel(0, 0); etRound: DoRound(0,0, PI); else DoSquare(0, 0); end; @@ -523,20 +514,7 @@ procedure TClipperOffset.OffsetOpenPath; AddPoint(fInPath[highI]); end else case fEndType of - etButt: - begin -{$IFDEF USINGZ} - with fInPath[highI] do AddPoint(Point64( - X - fNorms[highI].X *fGroupDelta, - Y - fNorms[highI].Y *fGroupDelta, - Z)); -{$ELSE} - with fInPath[highI] do AddPoint(Point64( - X - fNorms[highI].X *fGroupDelta, - Y - fNorms[highI].Y *fGroupDelta)); -{$ENDIF} - AddPoint(GetPerpendic(fInPath[highI], fNorms[highI], fGroupDelta)); - end; + etButt: DoBevel(highI, highI); etRound: DoRound(highI,highI, PI); else DoSquare(highI, highI); end; @@ -733,6 +711,31 @@ function ReflectPoint(const pt, pivot: TPointD): TPointD; end; //------------------------------------------------------------------------------ +procedure TClipperOffset.DoBevel(j, k: Integer); +var + absDelta: double; +begin + if k = j then + begin + absDelta := abs(fGroupDelta); + AddPoint( + fInPath[j].x - absDelta * fNorms[j].x, + fInPath[j].y - absDelta * fNorms[j].y); + AddPoint( + fInPath[j].x + absDelta * fNorms[j].x, + fInPath[j].y + absDelta * fNorms[j].y); + end else + begin + AddPoint( + fInPath[j].x + fGroupDelta * fNorms[k].x, + fInPath[j].y + fGroupDelta * fNorms[k].y); + AddPoint( + fInPath[j].x + fGroupDelta * fNorms[j].x, + fInPath[j].y + fGroupDelta * fNorms[j].y); + end; +end; +//------------------------------------------------------------------------------ + procedure TClipperOffset.DoSquare(j, k: Integer); var vec, pt1,pt2,pt3,pt4, pt,ptQ : TPointD; @@ -912,11 +915,13 @@ procedure TClipperOffset.OffsetPoint(j: Integer; var k: integer); if (cosA > fTmpLimit -1) then DoMiter(j, k, cosA) else DoSquare(j, k); end - else if (cosA > 0.99) or (fJoinType = jtSquare) then - //angle less than 8 degrees or squared joins - DoSquare(j, k) + else if (cosA > 0.99) or (fJoinType = jtBevel) then + // ie > 2.5 deg (see above) but less than ~8 deg ( acos(0.99) ) + DoBevel(j, k) + else if (fJoinType = jtRound) then + DoRound(j, k, ArcTan2(sinA, cosA)) else - DoRound(j, k, ArcTan2(sinA, cosA)); + DoSquare(j, k); k := j; end; diff --git a/Delphi/Examples/Example1/Example1.dpr b/Delphi/Examples/Example1/Example1.dpr index 068ec729..c3c067a8 100644 --- a/Delphi/Examples/Example1/Example1.dpr +++ b/Delphi/Examples/Example1/Example1.dpr @@ -48,4 +48,37 @@ begin end; ShellExecute(0, 'open','Sample1.svg', nil, nil, SW_SHOW); + setLength(subj, 1); + subj[0] := MakePath([40,60, 20,20, 180,20, 180,70, 25,150, 20,180, 180,180]); + solution := InflatePaths(subj, 20, jtMiter, etSquare, 3); + + with TSvgWriter.Create(fillRule) do + try + AddPaths(subj, true, $2000BBFF, $800033FF, 0.8); + AddPaths( solution, false, $2000FF00, $FF006600, 1.2, false); + AddText('Miter Joins; Square Ends', 10, 210); + + subj := Clipper.TranslatePaths(subj, 210, 0); + solution := InflatePaths(subj, 20, jtSquare, etSquare, 3); + AddPaths(subj, true, $2000BBFF, $800033FF, 0.8); + AddPaths( solution, false, $2000FF00, $FF006600, 1.2, false); + AddText('Square Joins; Square Ends', 220, 210); + + subj := Clipper.TranslatePaths(subj, 210, 0); + solution := InflatePaths(subj, 20, jtBevel, etButt, 3); + AddPaths(subj, true, $2000BBFF, $800033FF, 0.8); + AddPaths( solution, false, $2000FF00, $FF006600, 1.2, false); + AddText('Bevel Joins; Butt Ends', 430, 210); + + subj := Clipper.TranslatePaths(subj, 210, 0); + solution := InflatePaths(subj, 20, jtRound, etRound, 3); + AddPaths(subj, true, $2000BBFF, $800033FF, 0.8); + AddPaths( solution, false, $2000FF00, $FF006600, 1.2, false); + AddText('Round Joins; Round Ends', 640, 210); + SaveToFile('offsets.svg', 800,600, 40); + finally + free; + end; + ShellExecute(0, 'open','offsets.svg', nil, nil, SW_SHOW); + end. From dc3b90f661b9cd314ffe28313b1c5965b036a478 Mon Sep 17 00:00:00 2001 From: Juha Reunanen Date: Sun, 1 Oct 2023 04:03:14 +0300 Subject: [PATCH 46/95] C# only: truncate intersection point coordinates instead of rounding, to make the C# version work similarly to the C++ and Delphi versions (#664) --- CSharp/Clipper2Lib/Clipper.Core.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CSharp/Clipper2Lib/Clipper.Core.cs b/CSharp/Clipper2Lib/Clipper.Core.cs index e5818594..aefc8601 100644 --- a/CSharp/Clipper2Lib/Clipper.Core.cs +++ b/CSharp/Clipper2Lib/Clipper.Core.cs @@ -644,9 +644,13 @@ public static bool GetIntersectPoint(Point64 ln1a, } double t = ((ln1a.X - ln2a.X) * dy2 - (ln1a.Y - ln2a.Y) * dx2) / det; - if (t <= 0.0) ip = ln1a; + if (t <= 0.0) ip = ln1a; else if (t >= 1.0) ip = ln1b; - else ip = new Point64(ln1a.X + t * dx1, ln1a.Y + t * dy1); + else { + // NB: truncate the result instead of rounding it, to make the C# version work similarly to the C++ and Delphi versions + ip.X = (long) (ln1a.X + t * dx1); + ip.Y = (long) (ln1a.Y + t * dy1); + } return true; } From 8a3484f5d1b24b0b656f8753008f25916c820448 Mon Sep 17 00:00:00 2001 From: Juha Reunanen Date: Sun, 1 Oct 2023 04:58:21 +0300 Subject: [PATCH 47/95] C# only: specify rounding modes explicitly, so that they match the (SSE2 optimized) C++ version as accurately as possible (#660) --- CSharp/Clipper2Lib/Clipper.Core.cs | 43 +++++++++++++++------------- CSharp/Clipper2Lib/Clipper.Engine.cs | 4 ++- CSharp/Clipper2Lib/Clipper.cs | 6 ++-- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/CSharp/Clipper2Lib/Clipper.Core.cs b/CSharp/Clipper2Lib/Clipper.Core.cs index aefc8601..ada9bbb3 100644 --- a/CSharp/Clipper2Lib/Clipper.Core.cs +++ b/CSharp/Clipper2Lib/Clipper.Core.cs @@ -31,9 +31,9 @@ public Point64(Point64 pt) public Point64(Point64 pt, double scale) { - X = (long) Math.Round(pt.X * scale); - Y = (long) Math.Round(pt.Y * scale); - Z = (long) Math.Round(pt.Z * scale); + X = (long) Math.Round(pt.X * scale, MidpointRounding.AwayFromZero); + Y = (long) Math.Round(pt.Y * scale, MidpointRounding.AwayFromZero); + Z = (long) Math.Round(pt.Z * scale, MidpointRounding.AwayFromZero); } public Point64(long x, long y, long z = 0) @@ -45,22 +45,22 @@ public Point64(long x, long y, long z = 0) public Point64(double x, double y, double z = 0.0) { - X = (long) Math.Round(x); - Y = (long) Math.Round(y); - Z = (long) Math.Round(z); + X = (long) Math.Round(x, MidpointRounding.AwayFromZero); + Y = (long) Math.Round(y, MidpointRounding.AwayFromZero); + Z = (long) Math.Round(z, MidpointRounding.AwayFromZero); } public Point64(PointD pt) { - X = (long) Math.Round(pt.x); - Y = (long) Math.Round(pt.y); + X = (long) Math.Round(pt.x, MidpointRounding.AwayFromZero); + Y = (long) Math.Round(pt.y, MidpointRounding.AwayFromZero); Z = pt.z; } public Point64(PointD pt, double scale) { - X = (long) Math.Round(pt.x * scale); - Y = (long) Math.Round(pt.y * scale); + X = (long) Math.Round(pt.x * scale, MidpointRounding.AwayFromZero); + Y = (long) Math.Round(pt.y * scale, MidpointRounding.AwayFromZero); Z = pt.z; } @@ -104,26 +104,26 @@ public Point64(long x, long y) public Point64(double x, double y) { - X = (long) Math.Round(x); - Y = (long) Math.Round(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); - Y = (long) Math.Round(pt.y); + 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); - Y = (long) Math.Round(pt.Y * 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); - Y = (long) Math.Round(pt.y * 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) @@ -624,7 +624,7 @@ internal static double DotProduct(PointD vec1, PointD vec2) internal static long CheckCastInt64(double val) { if ((val >= max_coord) || (val <= min_coord)) return Invalid64; - return (long)Math.Round(val); + return (long)Math.Round(val, MidpointRounding.AwayFromZero); } @@ -686,7 +686,10 @@ public static Point64 GetClosestPtOnSegment(Point64 offPt, (offPt.Y - seg1.Y) * dy) / ((dx*dx) + (dy*dy)); if (q < 0) q = 0; else if (q > 1) q = 1; return new Point64( - seg1.X + Math.Round(q * dx), seg1.Y + Math.Round(q* dy)); + // use MidpointRounding.ToEven in order to explicitly match the nearbyint behaviour on the C++ side + seg1.X + Math.Round(q * dx, MidpointRounding.ToEven), + seg1.Y + Math.Round(q * dy, MidpointRounding.ToEven) + ); } public static PointInPolygonResult PointInPolygon(Point64 pt, Path64 polygon) diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index ecb3c8d4..b48b885d 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -508,7 +508,9 @@ private static long TopX(Active ae, long currentY) { if ((currentY == ae.top.Y) || (ae.top.X == ae.bot.X)) return ae.top.X; if (currentY == ae.bot.Y) return ae.bot.X; - return ae.bot.X + (long) Math.Round(ae.dx * (currentY - ae.bot.Y)); + + // use MidpointRounding.ToEven in order to explicitly match the nearbyint behaviour on the C++ side + return ae.bot.X + (long) Math.Round(ae.dx * (currentY - ae.bot.Y), MidpointRounding.ToEven); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/CSharp/Clipper2Lib/Clipper.cs b/CSharp/Clipper2Lib/Clipper.cs index dc7ccf64..ca2f8553 100644 --- a/CSharp/Clipper2Lib/Clipper.cs +++ b/CSharp/Clipper2Lib/Clipper.cs @@ -345,10 +345,10 @@ public static Point64 ScalePoint64(Point64 pt, double scale) { Point64 result = new Point64() { - X = (long) (pt.X * scale), - Y = (long) (pt.Y * scale), + X = (long) Math.Round(pt.X * scale, MidpointRounding.AwayFromZero), + Y = (long) Math.Round(pt.Y * scale, MidpointRounding.AwayFromZero), #if USINGZ - Z = (long) (pt.Z), + Z = (long) Math.Round(pt.Z, MidpointRounding.AwayFromZero), #endif }; return result; From 99eaaf1a8b7adea65246ff9c4fe24e1eaab3488e Mon Sep 17 00:00:00 2001 From: Daniel Lutz <26437097+lutz@users.noreply.github.com> Date: Sun, 1 Oct 2023 04:03:53 +0200 Subject: [PATCH 48/95] .net Standard 2.0 (#652) * create snk for signing * use source of hashcode to allow .net standard 2.0 * use .net standard 2.0 --- CSharp/Clipper2Lib/Clipper2Lib.csproj | 2 +- CSharp/Clipper2Lib/HashCode.cs | 126 ++++++++++++++++++ CSharp/Utils/ClipFileIO/Clipper.FileIO.csproj | 30 ++--- CSharp/Utils/SVG/Clipper2.SVG.csproj | 28 ++-- 4 files changed, 156 insertions(+), 30 deletions(-) create mode 100644 CSharp/Clipper2Lib/HashCode.cs diff --git a/CSharp/Clipper2Lib/Clipper2Lib.csproj b/CSharp/Clipper2Lib/Clipper2Lib.csproj index 8e75b825..9a4f2c5f 100644 --- a/CSharp/Clipper2Lib/Clipper2Lib.csproj +++ b/CSharp/Clipper2Lib/Clipper2Lib.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + netstandard2.0 enable 8 True diff --git a/CSharp/Clipper2Lib/HashCode.cs b/CSharp/Clipper2Lib/HashCode.cs new file mode 100644 index 00000000..dd209738 --- /dev/null +++ b/CSharp/Clipper2Lib/HashCode.cs @@ -0,0 +1,126 @@ +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; + +namespace Clipper2Lib +{ + /* + + Licensed to the .NET Foundation under one or more agreements. + // The .NET Foundation licenses this file to you under the MIT license. + + The xxHash32 implementation is based on the code published by Yann Collet: + https://raw.githubusercontent.com/Cyan4973/xxHash/5c174cfa4e45a42f94082dc0d4539b39696afea1/xxhash.c + + xxHash - Fast Hash algorithm + Copyright (C) 2012-2016, Yann Collet + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + 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 source repository : https://github.com/Cyan4973/xxHash + */ + + public struct HashCode + { + private static readonly uint s_seed = GenerateGlobalSeed(); + + private const uint Prime1 = 2654435761U; + private const uint Prime2 = 2246822519U; + private const uint Prime3 = 3266489917U; + private const uint Prime4 = 668265263U; + private const uint Prime5 = 374761393U; + + private static uint GenerateGlobalSeed() + { + using RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create(); + byte[] data = new byte[sizeof(uint)]; + randomNumberGenerator.GetBytes(data); + return BitConverter.ToUInt32(data, 0); + } + + public static int Combine(T1 value1, T2 value2) + { + uint hc1 = (uint) (value1?.GetHashCode() ?? 0); + uint hc2 = (uint) (value2?.GetHashCode() ?? 0); + + uint hash = MixEmptyState(); + hash += 8; + + hash = QueueRound(hash, hc1); + hash = QueueRound(hash, hc2); + + hash = MixFinal(hash); + return (int) hash; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint QueueRound(uint hash, uint queuedValue) + { + return RotateLeft(hash + (queuedValue * Prime3), 17) * Prime4; + } + + private static uint MixEmptyState() + { + return s_seed + Prime5; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixFinal(uint hash) + { + hash ^= hash >> 15; + hash *= Prime2; + hash ^= hash >> 13; + hash *= Prime3; + hash ^= hash >> 16; + return hash; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint RotateLeft(uint value, int offset) + { + return (value << offset) | (value >> (32 - offset)); + } + +#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member + [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + { + throw new NotSupportedException($"{nameof(HashCode)}.{nameof(GetHashCode)}() is not supported"); + } + + [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) + { + throw new NotSupportedException($"{nameof(HashCode)}.{nameof(Equals)}() is not supported"); + } +#pragma warning restore CS0809 // Obsolete member overrides non-obsolete member + } +} \ No newline at end of file diff --git a/CSharp/Utils/ClipFileIO/Clipper.FileIO.csproj b/CSharp/Utils/ClipFileIO/Clipper.FileIO.csproj index dcc7d1ee..50721f6a 100644 --- a/CSharp/Utils/ClipFileIO/Clipper.FileIO.csproj +++ b/CSharp/Utils/ClipFileIO/Clipper.FileIO.csproj @@ -1,15 +1,15 @@ - - - - netstandard2.1 - enable - 8 - AnyCPU;x86 - - - - - - - - + + + + netstandard2.0 + enable + 8 + AnyCPU;x86 + + + + + + + + diff --git a/CSharp/Utils/SVG/Clipper2.SVG.csproj b/CSharp/Utils/SVG/Clipper2.SVG.csproj index 025e5fba..361a3ec0 100644 --- a/CSharp/Utils/SVG/Clipper2.SVG.csproj +++ b/CSharp/Utils/SVG/Clipper2.SVG.csproj @@ -1,14 +1,14 @@ - - - - netstandard2.1 - enable - 8 - AnyCPU;x86 - - - - - - - + + + + netstandard2.0 + enable + 8 + AnyCPU;x86 + + + + + + + From 5dc364a1eda60aedd99e6d15bfc992af8aaab71b Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 1 Oct 2023 18:19:11 +1000 Subject: [PATCH 49/95] Minor code tidy (C# only) --- CSharp/Clipper2Lib/Clipper.Core.cs | 17 ++++++++++------- CSharp/Clipper2Lib/Clipper.Engine.cs | 19 +++---------------- CSharp/Clipper2Lib/Clipper.cs | 18 +++++++----------- CSharp/USINGZ/Clipper2LibZ.csproj | 3 ++- 4 files changed, 22 insertions(+), 35 deletions(-) diff --git a/CSharp/Clipper2Lib/Clipper.Core.cs b/CSharp/Clipper2Lib/Clipper.Core.cs index ada9bbb3..b1f95333 100644 --- a/CSharp/Clipper2Lib/Clipper.Core.cs +++ b/CSharp/Clipper2Lib/Clipper.Core.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 19 September 2023 * +* Date : 1 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core structures and functions for the Clipper Library * @@ -145,20 +145,20 @@ public Point64(PointD pt, double scale) { return new Point64(lhs.X - rhs.X, lhs.Y - rhs.Y); } - public override readonly string ToString() + public readonly override string ToString() { return $"{X},{Y} "; // nb: trailing space } #endif - public override readonly bool Equals(object? obj) + public readonly override bool Equals(object? obj) { if (obj != null && obj is Point64 p) return this == p; return false; } - public override readonly int GetHashCode() + public readonly override int GetHashCode() { return HashCode.Combine(X, Y); //#599 } @@ -275,7 +275,7 @@ public readonly string ToString(int precision = 2) !InternalClipper.IsAlmostZero(lhs.y - rhs.y); } - public override readonly bool Equals(object? obj) + public readonly override bool Equals(object? obj) { if (obj != null && obj is PointD p) return this == p; @@ -284,7 +284,7 @@ public override readonly bool Equals(object? obj) public void Negate() { x = -x; y = -y; } - public override readonly int GetHashCode() + public readonly override int GetHashCode() { return HashCode.Combine(x, y); //#599 } @@ -647,9 +647,12 @@ public static bool GetIntersectPoint(Point64 ln1a, if (t <= 0.0) ip = ln1a; else if (t >= 1.0) ip = ln1b; else { - // NB: truncate the result instead of rounding it, to make the C# version work similarly to the C++ and Delphi versions + // avoid using constructor (and rounding too) as they affect performance //664 ip.X = (long) (ln1a.X + t * dx1); ip.Y = (long) (ln1a.Y + t * dy1); +#if USINGZ + ip.Z = 0; +#endif } return true; } diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index b48b885d..91b07835 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 19 September 2023 * +* Date : 1 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -848,17 +848,6 @@ private LocalMinima PopLocalMinima() { return _minimaList[_currentLocMin++]; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void AddLocMin(Vertex vert, PathType polytype, bool isOpen) - { - // make sure the vertex is added only once ... - if ((vert.flags & VertexFlags.LocalMin) != VertexFlags.None) return; - vert.flags |= VertexFlags.LocalMin; - - LocalMinima lm = new LocalMinima(vert, polytype, isOpen); - _minimaList.Add(lm); - } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddSubject(Path64 path) @@ -1903,7 +1892,7 @@ private void AddNewIntersectNode(Active ae1, Active ae2, long topY) { if (!InternalClipper.GetIntersectPoint( ae1.bot, ae1.top, ae2.bot, ae2.top, out Point64 ip)) - ip = new Point64(ae1.curX, topY); + ip = new Point64(ae1.curX, topY); if (ip.Y > _currentBotY || ip.Y < topY) { @@ -2752,9 +2741,7 @@ private void ProcessHorzJoins() if (Path1InsidePath2(or1.pts, or2.pts)) { //swap or1's & or2's pts - OutPt tmp = or1.pts; - or1.pts = or2.pts; - or2.pts = tmp; + (or2.pts, or1.pts) = (or1.pts, or2.pts); FixOutRecPts(or1); FixOutRecPts(or2); //or2 is now inside or1 diff --git a/CSharp/Clipper2Lib/Clipper.cs b/CSharp/Clipper2Lib/Clipper.cs index ca2f8553..3bf1624f 100644 --- a/CSharp/Clipper2Lib/Clipper.cs +++ b/CSharp/Clipper2Lib/Clipper.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 July 2023 * +* Date : 1 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module contains simple functions that will likely cover * @@ -169,8 +169,7 @@ public static Paths64 RectClip(Rect64 rect, Paths64 paths) public static Paths64 RectClip(Rect64 rect, Path64 path) { if (rect.IsEmpty() || path.Count == 0) return new Paths64(); - Paths64 tmp = new Paths64(); - tmp.Add(path); + Paths64 tmp = new Paths64 { path }; return RectClip(rect, tmp); } @@ -189,8 +188,7 @@ public static PathsD RectClip(RectD rect, PathsD paths, int precision = 2) public static PathsD RectClip(RectD rect, PathD path, int precision = 2) { if (rect.IsEmpty() || path.Count == 0) return new PathsD(); - PathsD tmp = new PathsD(); - tmp.Add(path); + PathsD tmp = new PathsD { path }; return RectClip(rect, tmp, precision); } public static Paths64 RectClipLines(Rect64 rect, Paths64 paths) @@ -203,8 +201,7 @@ public static Paths64 RectClipLines(Rect64 rect, Paths64 paths) public static Paths64 RectClipLines(Rect64 rect, Path64 path) { if (rect.IsEmpty() || path.Count == 0) return new Paths64(); - Paths64 tmp = new Paths64(); - tmp.Add(path); + Paths64 tmp = new Paths64 { path }; return RectClipLines(rect, tmp); } @@ -223,8 +220,7 @@ public static PathsD RectClipLines(RectD rect, public static PathsD RectClipLines(RectD rect, PathD path, int precision = 2) { if (rect.IsEmpty() || path.Count == 0) return new PathsD(); - PathsD tmp = new PathsD(); - tmp.Add(path); + PathsD tmp = new PathsD { path }; return RectClipLines(rect, tmp, precision); } public static Paths64 MinkowskiSum(Path64 pattern, Path64 path, bool isClosed) @@ -863,7 +859,7 @@ public static Path64 SimplifyPath(Path64 path, bool[] flags = new bool[len]; double[] dsq = new double[len]; - int prev = high, curr = 0, start, next, prior2, next2; + int curr = 0, prev, start, next, prior2, next2; if (isClosedPath) { @@ -940,7 +936,7 @@ public static PathD SimplifyPath(PathD path, bool[] flags = new bool[len]; double[] dsq = new double[len]; - int prev = high, curr = 0, start, next, prior2, next2; + int curr = 0, prev, start, next, prior2, next2; if (isOpenPath) { dsq[0] = double.MaxValue; diff --git a/CSharp/USINGZ/Clipper2LibZ.csproj b/CSharp/USINGZ/Clipper2LibZ.csproj index 15fa9407..98de5d5d 100644 --- a/CSharp/USINGZ/Clipper2LibZ.csproj +++ b/CSharp/USINGZ/Clipper2LibZ.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + netstandard2.0 enable 8 @@ -15,6 +15,7 @@ + From 1e46e2bd9d1a10a9aa68384cd5423694a9cb0e7a Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 1 Oct 2023 18:38:54 +1000 Subject: [PATCH 50/95] netstandard2.0 (C#) --- CSharp/Utils/Colors/Clipper.Colors.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CSharp/Utils/Colors/Clipper.Colors.csproj b/CSharp/Utils/Colors/Clipper.Colors.csproj index 025e5fba..91f9c196 100644 --- a/CSharp/Utils/Colors/Clipper.Colors.csproj +++ b/CSharp/Utils/Colors/Clipper.Colors.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + netstandard2.0 enable 8 AnyCPU;x86 From b37c974220f806ffecc5498a6ada3bbaf371ba29 Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 1 Oct 2023 20:00:28 +1000 Subject: [PATCH 51/95] version 1.2.3 --- CPP/CMakeLists.txt | 2 +- CPP/Clipper2Lib/include/clipper2/clipper.engine.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CPP/CMakeLists.txt b/CPP/CMakeLists.txt index 84f8e263..af12a390 100644 --- a/CPP/CMakeLists.txt +++ b/CPP/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.15) -project(Clipper2 VERSION 1.2.2 LANGUAGES C CXX) +project(Clipper2 VERSION 1.2.3 LANGUAGES C CXX) set(CMAKE_POSITION_INDEPENDENT_CODE ON) if(NOT DEFINED CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 17) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h index 5aca121b..6d97539f 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 July 2023 * +* Date : 6 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -10,7 +10,7 @@ #ifndef CLIPPER_ENGINE_H #define CLIPPER_ENGINE_H -constexpr auto CLIPPER2_VERSION = "1.2.2"; +constexpr auto CLIPPER2_VERSION = "1.2.3"; #include #include //#541 From c7a48296ee90b78a71eaaa5b947583479d88ba8c Mon Sep 17 00:00:00 2001 From: philstopford Date: Tue, 3 Oct 2023 18:28:12 -0500 Subject: [PATCH 52/95] Update Clipper.cs (#673) --- CSharp/Clipper2Lib/Clipper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CSharp/Clipper2Lib/Clipper.cs b/CSharp/Clipper2Lib/Clipper.cs index 3bf1624f..7bf09fa9 100644 --- a/CSharp/Clipper2Lib/Clipper.cs +++ b/CSharp/Clipper2Lib/Clipper.cs @@ -344,7 +344,7 @@ public static Point64 ScalePoint64(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, MidpointRounding.AwayFromZero), + Z = pt.Z, #endif }; return result; From 763526bf5c306bfc195d578fc15e3a166f4fd1e6 Mon Sep 17 00:00:00 2001 From: angusj Date: Wed, 4 Oct 2023 14:11:32 +1000 Subject: [PATCH 53/95] Added CMake updated clipper.version.h file Minor tweaks to PR #675 --- CPP/CMakeLists.txt | 22 +++++++++++++------ .../include/clipper2/clipper.core.h | 9 +++++++- .../include/clipper2/clipper.engine.h | 4 +--- .../include/clipper2/clipper.version.h | 6 +++++ CPP/clipper.version.in | 6 +++++ CSharp/Clipper2Lib/Clipper.cs | 2 +- 6 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 CPP/Clipper2Lib/include/clipper2/clipper.version.h create mode 100644 CPP/clipper.version.in diff --git a/CPP/CMakeLists.txt b/CPP/CMakeLists.txt index af12a390..257079ea 100644 --- a/CPP/CMakeLists.txt +++ b/CPP/CMakeLists.txt @@ -17,16 +17,24 @@ option(USE_EXTERNAL_GBENCHMARK "Use the googlebenchmark" OFF) option(BUILD_SHARED_LIBS "Build shared libs" OFF) set(CLIPPER2_USINGZ "ON" CACHE STRING "Build Clipper2Z, either \"ON\" or \"OFF\" or \"ONLY\"") +if (APPLE) + set(CMAKE_SHARED_LIBRARY_SUFFIX ".so") +endif () + include(GNUInstallDirs) +set(CLIPPER2_INC_FOLDER ${PROJECT_SOURCE_DIR}/Clipper2Lib/include/clipper2) +configure_file(clipper.version.in ${CLIPPER2_INC_FOLDER}/clipper.version.h) + set(CLIPPER2_INC - Clipper2Lib/include/clipper2/clipper.h - Clipper2Lib/include/clipper2/clipper.core.h - Clipper2Lib/include/clipper2/clipper.engine.h - Clipper2Lib/include/clipper2/clipper.export.h - Clipper2Lib/include/clipper2/clipper.minkowski.h - Clipper2Lib/include/clipper2/clipper.offset.h - Clipper2Lib/include/clipper2/clipper.rectclip.h + ${CLIPPER2_INC_FOLDER}/clipper.h + ${CLIPPER2_INC_FOLDER}/clipper.version.h + ${CLIPPER2_INC_FOLDER}/clipper.core.h + ${CLIPPER2_INC_FOLDER}/clipper.engine.h + ${CLIPPER2_INC_FOLDER}/clipper.export.h + ${CLIPPER2_INC_FOLDER}/clipper.minkowski.h + ${CLIPPER2_INC_FOLDER}/clipper.offset.h + ${CLIPPER2_INC_FOLDER}/clipper.rectclip.h ) set(CLIPPER2_SRC diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index 7a32d4c4..d1849cad 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 July 2023 * +* Date : 4 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library structures and functions * @@ -20,6 +20,8 @@ #include #include +#include "clipper2/clipper.version.h" + namespace Clipper2Lib { @@ -309,6 +311,11 @@ namespace Clipper2Lib ((std::max)(top, rec.top) <= (std::min)(bottom, rec.bottom)); }; + bool operator==(const Rect& other) const { + return left == other.left && right == other.right && + top == other.top && bottom == other.bottom; + } + friend std::ostream& operator<<(std::ostream& os, const Rect& rect) { os << "(" << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h index 6d97539f..152c98a6 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 6 October 2023 * +* Date : 1 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -10,8 +10,6 @@ #ifndef CLIPPER_ENGINE_H #define CLIPPER_ENGINE_H -constexpr auto CLIPPER2_VERSION = "1.2.3"; - #include #include //#541 #include diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.version.h b/CPP/Clipper2Lib/include/clipper2/clipper.version.h new file mode 100644 index 00000000..638b222a --- /dev/null +++ b/CPP/Clipper2Lib/include/clipper2/clipper.version.h @@ -0,0 +1,6 @@ +#ifndef CLIPPER_VERSION_H +#define CLIPPER_VERSION_H + +constexpr auto CLIPPER2_VERSION = "1.2.3"; + +#endif // CLIPPER_VERSION_H diff --git a/CPP/clipper.version.in b/CPP/clipper.version.in new file mode 100644 index 00000000..fb75c48d --- /dev/null +++ b/CPP/clipper.version.in @@ -0,0 +1,6 @@ +#ifndef CLIPPER_VERSION_H +#define CLIPPER_VERSION_H + +constexpr auto CLIPPER2_VERSION = "@PROJECT_VERSION@"; + +#endif // CLIPPER_VERSION_H diff --git a/CSharp/Clipper2Lib/Clipper.cs b/CSharp/Clipper2Lib/Clipper.cs index 7bf09fa9..af6a077c 100644 --- a/CSharp/Clipper2Lib/Clipper.cs +++ b/CSharp/Clipper2Lib/Clipper.cs @@ -344,7 +344,7 @@ public static Point64 ScalePoint64(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 = pt.Z, + Z = pt.Z #endif }; return result; From a6ffab2918530bba7f0f354a44d57802aa4d2b03 Mon Sep 17 00:00:00 2001 From: Vyacheslav Zhdanovskiy Date: Fri, 6 Oct 2023 02:18:07 +0300 Subject: [PATCH 54/95] Fix cyclic header file dependency (#677) --- CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h | 1 - 1 file changed, 1 deletion(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h b/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h index bcbe7f43..b6861c61 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h @@ -13,7 +13,6 @@ #include #include #include -#include "clipper.h" #include "clipper.core.h" namespace Clipper2Lib From 91bf3c60e55ed4baaaf715ad1d583b550a125f6f Mon Sep 17 00:00:00 2001 From: angusj Date: Fri, 6 Oct 2023 10:32:04 +1000 Subject: [PATCH 55/95] Fixed a bug in polytree nesting (#679) --- Delphi/Clipper2Lib/Clipper.Engine.pas | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index e78e7f7c..1e41b8b4 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 27 August 2023 * +* Date : 6 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -862,6 +862,7 @@ function GetCleanPath(op: POutPt): TPath64; ((op2.pt.Y <> op2.next.pt.Y) or (op2.pt.Y <> prevOp.pt.Y))) then begin result[cnt] := op2.pt; + inc(cnt); prevOp := op2; end; op2 := op2.next; From 6a7a401e70d65816f2e86713648ed757895d1df8 Mon Sep 17 00:00:00 2001 From: angusj Date: Mon, 16 Oct 2023 16:10:37 +1000 Subject: [PATCH 56/95] Fixed a Delphi only bug in Polytree path ownership (#687) --- Delphi/Clipper2Lib/Clipper.Engine.pas | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index 1e41b8b4..eca93e62 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 6 October 2023 * +* Date : 16 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -959,11 +959,16 @@ function Path1InsidePath2(const op1, op2: POutPt): Boolean; else if pipResult = pipInside then dec(outsideCnt); op := op.next; until (op = op1) or (Abs(outsideCnt) = 2); - // if path1's location is still equivocal then check its midpoint - path := GetCleanPath(op1); - mp := Clipper.Core.GetBounds(path).MidPoint; - path := GetCleanPath(op2); - Result := PointInPolygon(mp, path) <> pipOutside; + if (Abs(outsideCnt) < 2) then + begin + // if path1's location is still equivocal then check its midpoint + path := GetCleanPath(op1); + mp := Clipper.Core.GetBounds(path).MidPoint; + path := GetCleanPath(op2); + Result := PointInPolygon(mp, path) <> pipOutside; + end + else + Result := (outsideCnt < 0); end; //------------------------------------------------------------------------------ From 0b5bf2f631ad42d27b6089c0d8bf02153679d9e6 Mon Sep 17 00:00:00 2001 From: angusj Date: Thu, 19 Oct 2023 20:08:51 +1000 Subject: [PATCH 57/95] Fixed a very minor bug merging touching polygons (Dis.#690) --- CPP/Clipper2Lib/src/clipper.engine.cpp | 7 +++++-- CSharp/Clipper2Lib/Clipper.Engine.cs | 4 +++- Delphi/Clipper2Lib/Clipper.Engine.pas | 20 +++++--------------- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index bed48b15..0e1d8de9 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 27 August 2023 * +* Date : 19 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -1469,13 +1469,14 @@ namespace Clipper2Lib { e2.outrec->front_edge = nullptr; e2.outrec->back_edge = nullptr; e2.outrec->pts = nullptr; - SetOwner(e2.outrec, e1.outrec); if (IsOpenEnd(e1)) { e2.outrec->pts = e1.outrec->pts; e1.outrec->pts = nullptr; } + else + SetOwner(e2.outrec, e1.outrec); //and e1 and e2 are maxima and are about to be dropped from the Actives list. e1.outrec = nullptr; @@ -2591,6 +2592,7 @@ namespace Clipper2Lib { { IntersectEdges(horz, *e, pt); SwapPositionsInAEL(horz, *e); + CheckJoinLeft(*e, pt); horz.curr_x = e->curr_x; e = horz.next_in_ael; } @@ -2598,6 +2600,7 @@ namespace Clipper2Lib { { IntersectEdges(*e, horz, pt); SwapPositionsInAEL(*e, horz); + CheckJoinRight(*e, pt); horz.curr_x = e->curr_x; e = horz.prev_in_ael; } diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 91b07835..9f4a4646 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 1 October 2023 * +* Date : 19 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2228,6 +2228,7 @@ private void DoHorizontal(Active horz) { IntersectEdges(horz, ae, pt); SwapPositionsInAEL(horz, ae); + CheckJoinLeft(ae, pt); horz.curX = ae.curX; ae = horz.nextInAEL; } @@ -2235,6 +2236,7 @@ private void DoHorizontal(Active horz) { IntersectEdges(ae, horz, pt); SwapPositionsInAEL(ae, horz); + CheckJoinRight(ae, pt); horz.curX = ae.curX; ae = horz.prevInAEL; } diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index eca93e62..416a18aa 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 16 October 2023 * +* Date : 19 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2342,22 +2342,10 @@ procedure TClipperBase.JoinOutrecPaths(e1, e2: PActive); begin e2.outrec.pts := e1.outrec.pts; e1.outrec.pts := nil; - end else - begin + end + else SetOwner(e2.outrec, e1.outrec); -// if FUsingPolytree then -// begin -// e := GetPrevHotEdge(e1); -// if not Assigned(e) then -// outRec.owner := nil else -// SetOwner(outRec, e.outrec); -// // nb: outRec.owner here is likely NOT the real -// // owner but this will be checked in DeepCheckOwner() -// end; - - end; - // and e1 and e2 are maxima and are about to be dropped from the Actives list. e1.outrec := nil; e2.outrec := nil; @@ -3539,12 +3527,14 @@ procedure TClipperBase.DoHorizontal(horzEdge: PActive); begin IntersectEdges(horzEdge, e, pt); SwapPositionsInAEL(horzEdge, e); + CheckJoinLeft(e, pt); horzEdge.currX := e.currX; e := horzEdge.nextInAEL; end else begin IntersectEdges(e, horzEdge, pt); SwapPositionsInAEL(e, horzEdge); + CheckJoinRight(e, pt); horzEdge.currX := e.currX; e := horzEdge.prevInAEL; end; From 9ffd0778bd7344ac9f83307b8895ddb1b0020c12 Mon Sep 17 00:00:00 2001 From: angusj Date: Wed, 25 Oct 2023 07:50:39 +1000 Subject: [PATCH 58/95] Major rewrite of clipper.export.h and also renamed a couple of exported functions. Delphi DLL sample app updated too --- .../include/clipper2/clipper.engine.h | 11 +- .../include/clipper2/clipper.export.h | 806 ++++++++---------- CPP/Utils/CommonUtils.h | 10 + CSharp/Clipper2Lib/Clipper.Engine.cs | 18 +- DLL/Delphi_TestApp/Test_DLL.dpr | 771 +++++++---------- Delphi/Clipper2Lib/Clipper.Core.pas | 2 +- Delphi/Clipper2Lib/Clipper.Engine.pas | 20 +- 7 files changed, 674 insertions(+), 964 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h index 152c98a6..137f021e 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 1 October 2023 * +* Date : 24 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -416,6 +416,7 @@ namespace Clipper2Lib { void SetScale(double value) { scale_ = value; } double Scale() { return scale_; } + PolyPathD* AddChild(const Path64& path) override { int error_code = 0; @@ -425,6 +426,14 @@ namespace Clipper2Lib { return result; } + 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; + } + void Clear() override { childs_.resize(0); diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index f5f81d2a..748cb21c 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -1,39 +1,45 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 30 May 2023 * +* Date : 24 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module exports the Clipper2 Library (ie DLL/so) * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ -// The exported functions below refer to simple structures that -// can be understood across multiple languages. Consequently -// Path64, PathD, Polytree64 etc are converted from C++ classes -// (std::vector<> etc) into the following data structures: +// ============================================================================= +// CAUTION: THE FOLLOWING DATA STRUCTURES HAVE RECENTLY BEEN CHANGED (ver 1.2.4) +// ============================================================================= +// +// The path structures that are used extensively in other parts of this library +// are all based on std::vector classes. And unfortunately, C++ classes can't +// be accessed by other languages. So all these (std::vector based) paths must +// be converted into simple C data structures that can be understood by just +// about any programming language. And these C style data structures are mostly +// just simple arrays. // -// CPath64 (int64_t*) & CPathD (double_t*): -// Path64 and PathD are converted into arrays of x,y coordinates. -// However in these arrays the first x,y coordinate pair is a -// counter with 'x' containing the number of following coordinate -// pairs. ('y' should be 0, with one exception explained below.) +// Path64 and PathD are converted into arrays of int64_t or double values +// respectively, representing consecutive x and y coordinates. +// However preceeding each array is single x,y pair that contains the +// path's length in the x value (and the 'y' value = 0). // __________________________________ // |counter|coord1|coord2|...|coordN| -// |N ,0 |x1, y1|x2, y2|...|xN, yN| +// |N, 0 |x1, y1|x2, y2|...|xN, yN| // __________________________________ // -// CPaths64 (int64_t**) & CPathsD (double_t**): -// These are arrays of pointers to CPath64 and CPathD where -// the first pointer is to a 'counter path'. This 'counter -// path' has a single x,y coord pair with 'y' (not 'x') -// containing the number of paths that follow. ('x' = 0). +// CPaths64 and CPathsD: +// These structures are very similar to their respecitve path structures. These +// structures not only contain any number of consecutive CPath64 or CPathD +// structures but, preceeding these paths, there is an extra x,y pair of values +// that contains the path count. However, in this case the x value = 0, and +// the y value contains the count (ie the number of following paths). // _______________________________ // |counter|path1|path2|...|pathN| -// |addr0 |addr1|addr2|...|addrN| (*addr0[0]=0; *addr0[1]=N) +// |0, N | | |...|pathN| // _______________________________ // -// The structures of CPolytree64 and CPolytreeD are defined -// below and these structures don't need to be explained here. +// +// The CPolytree64 & CPolytreeD structures are defined lower down. #ifndef CLIPPER2_EXPORT_H #define CLIPPER2_EXPORT_H @@ -49,25 +55,20 @@ namespace Clipper2Lib { typedef int64_t* CPath64; -typedef int64_t** CPaths64; -typedef double* CPathD; -typedef double** CPathsD; +typedef int64_t* CPaths64; +typedef double* CPathD; +typedef double* CPathsD; -typedef struct CPolyPath64 { - CPath64 polygon; - uint32_t is_hole; - uint32_t child_count; - CPolyPath64* childs; -} -CPolyTree64; +static const int64_t magic_64 = 64; +static const double magic_D = 68; -typedef struct CPolyPathD { - CPathD polygon; - uint32_t is_hole; - uint32_t child_count; - CPolyPathD* childs; -} -CPolyTreeD; +typedef int64_t* CPolyPath64; +typedef int64_t* CPolyTree64; +// magic, hole, child_count, poly_len (4 total) + polygon + nested childs + +typedef double* CPolyPathD; +typedef double* CPolyTreeD; +// magic, hole, child_count, poly_len (4 total) + polygon + nested childs template struct CRect { @@ -100,26 +101,35 @@ inline Rect CRectToRect(const CRect& rect) #define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport) ////////////////////////////////////////////////////// -// EXPORTED FUNCTION DEFINITIONS +// EXPORTED FUNCTION DECLARATIONS ////////////////////////////////////////////////////// EXTERN_DLL_EXPORT const char* Version(); -// Some of the functions below will return data in the various CPath -// and CPolyTree structures which are pointers to heap allocated -// memory. Eventually this memory will need to be released with one -// of the following 'DisposeExported' functions. (This may be the -// only safe way to release this memory since the executable -// accessing these exported functions may use a memory manager that -// allocates and releases heap memory in a different way. Also, -// CPath structures that have been constructed by the executable -// should not be destroyed using these 'DisposeExported' functions.) -EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p); -EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& pp); -EXTERN_DLL_EXPORT void DisposeExportedCPathD(CPathD p); -EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& pp); -EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64*& cpt); -EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD*& cpt); +// Some exported functions will return data in structures that +// have been allocated in heap memory. Eventually this memory will +// need to be released using one of the following 'DisposeExported...' +// functions. (This may be the only safe way to release this memory +// since the executable accessing these exported functions may use +// a memory manager that allocates and releases heap memory in a +// different way. +EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& p) +{ + delete[] p; +} +EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& p) +{ + delete[] p; +} +EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64& p) +{ + delete[] p; +} +EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD& p) +{ + delete[] p; +} + // Boolean clipping: // cliptype: None=0, Intersection=1, Union=2, Difference=3, Xor=4 @@ -129,20 +139,23 @@ EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, const CPaths64 subjects_open, const CPaths64 clips, CPaths64& solution, CPaths64& solution_open, bool preserve_collinear = true, bool reverse_solution = false); -EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype, + +EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype, uint8_t fillrule, const CPaths64 subjects, const CPaths64 subjects_open, const CPaths64 clips, - CPolyTree64*& solution, CPaths64& solution_open, + int64_t*& sol_tree, CPaths64& solution_open, bool preserve_collinear = true, bool reverse_solution = false); + EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, uint8_t fillrule, const CPathsD subjects, const CPathsD subjects_open, const CPathsD clips, CPathsD& solution, CPathsD& solution_open, int precision = 2, bool preserve_collinear = true, bool reverse_solution = false); -EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype, + +EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype, uint8_t fillrule, const CPathsD subjects, const CPathsD subjects_open, const CPathsD clips, - CPolyTreeD*& solution, CPathsD& solution_open, int precision = 2, + CPolyTreeD& solution, CPathsD& solution_open, int precision = 2, bool preserve_collinear = true, bool reverse_solution = false); // Polygon offsetting (inflate/deflate): @@ -171,64 +184,262 @@ EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, // INTERNAL FUNCTIONS ////////////////////////////////////////////////////// -inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2); -inline CPath64 CreateCPath64(const Path64& p); -inline CPaths64 CreateCPaths64(const Paths64& pp); -inline Path64 ConvertCPath64(const CPath64& p); -inline Paths64 ConvertCPaths64(const CPaths64& pp); +static void GetPathCountAndCPaths64ArrayLen(const Paths64& paths, size_t& cnt, size_t& array_len) +{ + array_len = 2; + cnt = 0; + for (const Path64& path : paths) + if (path.size()) + { + array_len += path.size() * 2 + 2; + ++cnt; + } +} -inline CPathD CreateCPathD(size_t cnt1, size_t cnt2); -inline CPathD CreateCPathD(const PathD& p); -inline CPathsD CreateCPathsD(const PathsD& pp); -inline PathD ConvertCPathD(const CPathD& p); -inline PathsD ConvertCPathsD(const CPathsD& pp); +static void GetPathCountAndCPathsDArrayLen(const PathsD& paths, size_t& cnt, size_t& array_len) +{ + array_len = 2; + cnt = 0; + for (const PathD& path : paths) + if (path.size()) + { + array_len += path.size() * 2 + 2; + ++cnt; + } +} -// the following function avoid multiple conversions -inline CPathD CreateCPathD(const Path64& p, double scale); -inline CPathsD CreateCPathsD(const Paths64& pp, double scale); -inline Path64 ConvertCPathD(const CPathD& p, double scale); -inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale); +static size_t GetPolyPath64ArrayLen(const PolyPath64& pp) +{ + size_t result = 4; // magic + is_hole + child_count + poly_length + result += pp.Polygon().size() * 2; + for (size_t i = 0; i < pp.Count(); ++i) + result += GetPolyPath64ArrayLen(*pp[i]); + return result; +} -inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt); -inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale); +static void GetPolytreeCountAndCStorageSize(const PolyTree64& tree, size_t& cnt, size_t& array_len) +{ + cnt = tree.Count(); // nb: top level count only + array_len = GetPolyPath64ArrayLen(tree); +} -EXTERN_DLL_EXPORT const char* Version() +static size_t GetPolyPathDArrayLen(const PolyPathD& pp) { - return CLIPPER2_VERSION; + size_t result = 4; // magic + is_hole + child_count + poly_length + result += pp.Polygon().size() * 2; + for (size_t i = 0; i < pp.Count(); ++i) + result += GetPolyPathDArrayLen(*pp[i]); } -EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p) +static CPaths64 CreateCPaths64(const Paths64& paths) { - if (p) delete[] p; + size_t cnt, array_len; + GetPathCountAndCPaths64ArrayLen(paths, cnt, array_len); + int64_t* result = new int64_t[array_len], * v = result; + *v++ = 0; + *v++ = cnt; + for (const Path64& path : paths) + { + if (!path.size()) continue; + *v++ = path.size(); + *v++ = 0; + for (const Point64& pt : path) + { + *v++ = pt.x; + *v++ = pt.y; + } + } + return result; } -EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& pp) +static CPathsD CreateCPathsD(const PathsD& paths) { - if (!pp) return; - CPaths64 v = pp; - CPath64 cnts = *v; - const size_t cnt = static_cast(cnts[1]); - for (size_t i = 0; i <= cnt; ++i) //nb: cnt +1 - DisposeExportedCPath64(*v++); - delete[] pp; - pp = nullptr; + size_t cnt, array_len; + GetPathCountAndCPathsDArrayLen(paths, cnt, array_len); + double* result = new double[array_len], * v = result; + *v++ = 0; + *v++ = (double)cnt; + for (const PathD& path : paths) + { + if (!path.size()) continue; + *v++ = (double)path.size(); + *v++ = 0; + for (const PointD& pt : path) + { + *v++ = pt.x; + *v++ = pt.y; + } + } + return result; } -EXTERN_DLL_EXPORT void DisposeExportedCPathD(CPathD p) +CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale) { - if (p) delete[] p; + if (!paths.size()) return nullptr; + size_t cnt, array_len; + GetPathCountAndCPaths64ArrayLen(paths, cnt, array_len); + CPathsD result = new double[array_len], v = result; + *v++ = 0; + *v++ = (double)cnt; + for (const Path64& path : paths) + { + if (!path.size()) continue; + *v = (double)path.size(); + ++v; *v++ = 0; + for (const Point64& pt : path) + { + *v++ = pt.x * scale; + *v++ = pt.y * scale; + } + } + return result; } -EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& pp) +static Paths64 ConvertCPaths64(const CPaths64 paths) { - if (!pp) return; - CPathsD v = pp; - CPathD cnts = *v; - size_t cnt = static_cast(cnts[1]); - for (size_t i = 0; i <= cnt; ++i) //nb: cnt +1 - DisposeExportedCPathD(*v++); - delete[] pp; - pp = nullptr; + Paths64 result; + if (!paths) return result; + int64_t* v = paths; ++v; + size_t cnt = *v++; + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + { + size_t cnt2 = *v; + v += 2; + Path64 path; + path.reserve(cnt2); + for (size_t j = 0; j < cnt2; ++j) + { + int64_t x = *v++, y = *v++; + path.push_back(Point64(x,y)); + } + result.push_back(path); + } + return result; +} + +static PathsD ConvertCPathsD(const CPathsD paths) +{ + PathsD result; + if (!paths) return result; + double* v = paths; ++v; + size_t cnt = (size_t)*v++; + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + { + size_t cnt2 = (size_t)*v; + v += 2; + PathD path; + path.reserve(cnt2); + { + double x = *v++, y = *v++; + path.push_back(PointD(x, y)); + } + result.push_back(path); + } + return result; +} + +static Paths64 ConvertCPathsDToPaths64(const CPathsD paths, double scale) +{ + Paths64 result; + if (!paths) return result; + double* v = paths; + ++v; // skip the first value (0) + int64_t cnt = (int64_t)*v++; + result.reserve(cnt); + for (int i = 0; i < cnt; ++i) + { + int64_t cnt2 = (int64_t)*v; + v += 2; + Path64 path; + path.reserve(cnt2); + for (int j = 0; j < cnt2; ++j) + { + double x = *v++ * scale; + double y = *v++ * scale; + path.push_back(Point64(x, y)); + } + result.push_back(path); + } + return result; +} + +static void CreateCPolyPath64(const PolyPath64* pp, CPolyPath64& v) +{ + *v++ = magic_64; + *v++ = pp->IsHole() ? 1 : 0; + *v++ = pp->Count(); + *v++ = pp->Polygon().size(); + for (const Point64& pt : pp->Polygon()) + { + *v++ = pt.x; + *v++ = pt.y; + } + for (size_t i = 0; i < pp->Count(); ++i) + CreateCPolyPath64(pp->Child(i), v); +} + +static CPolyTree64 CreateCPolyTree64(const PolyTree64& tree) +{ + size_t cnt, array_len; + GetPolytreeCountAndCStorageSize(tree, cnt, array_len); + if (!cnt) return nullptr; + // allocate storage + int64_t* result = new int64_t[array_len]; + int64_t* v = &result[0]; + + *v++ = magic_64; + *v++ = 0; + *v++ = tree.Count(); + *v++ = 0; + for (size_t i = 0; i < tree.Count(); ++i) + CreateCPolyPath64(tree.Child(i), v); + return result; +} + +static void CreateCPolyPathD(const PolyPath64* pp, CPolyPathD& v, double scale) +{ + *v++ = magic_64; + *v++ = pp->IsHole() ? 1 : 0; + *v++ = (double)pp->Count(); + *v++ = (double)pp->Polygon().size(); + for (const Point64& pt : pp->Polygon()) + { + *v++ = pt.x * scale; + *v++ = pt.y * scale; + } + for (size_t i = 0; i < pp->Count(); ++i) + CreateCPolyPathD(pp->Child(i), v, scale); +} + + +static CPolyTreeD CreateCPolyTreeD(const PolyTree64& tree, double scale) +{ + size_t cnt, array_len; + GetPolytreeCountAndCStorageSize(tree, cnt, array_len); + if (!cnt) return nullptr; + // allocate storage + double* result = new double[array_len]; + double* v = &result[0]; + + *v++ = magic_64; + *v++ = 0; + *v++ = (double)tree.Count(); + *v++ = 0; + for (size_t i = 0; i < tree.Count(); ++i) + CreateCPolyPathD(tree.Child(i), v, scale); + return result; +} + + +////////////////////////////////////////////////////// +// EXPORTED FUNCTION DEFINITIONS +////////////////////////////////////////////////////// + +EXTERN_DLL_EXPORT const char* Version() +{ + return CLIPPER2_VERSION; } EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, @@ -258,10 +469,10 @@ EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, return 0; //success !! } -EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype, +EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype, uint8_t fillrule, const CPaths64 subjects, const CPaths64 subjects_open, const CPaths64 clips, - CPolyTree64*& solution, CPaths64& solution_open, + int64_t*& sol_tree, CPaths64& solution_open, bool preserve_collinear, bool reverse_solution) { if (cliptype > static_cast(ClipType::Xor)) return -4; @@ -271,17 +482,17 @@ EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype, sub_open = ConvertCPaths64(subjects_open); clp = ConvertCPaths64(clips); - PolyTree64 pt; + PolyTree64 tree; Clipper64 clipper; clipper.PreserveCollinear = preserve_collinear; clipper.ReverseSolution = reverse_solution; 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), pt, sol_open)) + if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open)) return -1; // clipping bug - should never happen :) - solution = CreateCPolyTree64(pt); + sol_tree = CreateCPolyTree64(tree); solution_open = CreateCPaths64(sol_open); return 0; //success !! } @@ -298,9 +509,9 @@ EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, const double scale = std::pow(10, precision); Paths64 sub, sub_open, clp, sol, sol_open; - sub = ConvertCPathsD(subjects, scale); - sub_open = ConvertCPathsD(subjects_open, scale); - clp = ConvertCPathsD(clips, scale); + sub = ConvertCPathsDToPaths64(subjects, scale); + sub_open = ConvertCPathsDToPaths64(subjects_open, scale); + clp = ConvertCPathsDToPaths64(clips, scale); Clipper64 clipper; clipper.PreserveCollinear = preserve_collinear; @@ -312,43 +523,42 @@ EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open)) return -1; - if (sol.size() > 0) solution = CreateCPathsD(sol, 1 / scale); - if (sol_open.size() > 0) - solution_open = CreateCPathsD(sol_open, 1 / scale); + solution = CreateCPathsDFromPaths64(sol, 1 / scale); + solution_open = CreateCPathsDFromPaths64(sol_open, 1 / scale); return 0; } -EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype, +EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype, uint8_t fillrule, const CPathsD subjects, const CPathsD subjects_open, const CPathsD clips, - CPolyTreeD*& solution, CPathsD& solution_open, int precision, + CPolyTreeD& solution, CPathsD& solution_open, int precision, bool preserve_collinear, bool reverse_solution) { if (precision < -8 || precision > 8) return -5; if (cliptype > static_cast(ClipType::Xor)) return -4; if (fillrule > static_cast(FillRule::Negative)) return -3; - const double scale = std::pow(10, precision); + double scale = std::pow(10, precision); + + int err = 0; Paths64 sub, sub_open, clp, sol_open; - sub = ConvertCPathsD(subjects, scale); - sub_open = ConvertCPathsD(subjects_open, scale); - clp = ConvertCPathsD(clips, scale); + sub = ConvertCPathsDToPaths64(subjects, scale); + sub_open = ConvertCPathsDToPaths64(subjects_open, scale); + clp = ConvertCPathsDToPaths64(clips, scale); - PolyTree64 sol; + PolyTree64 tree; Clipper64 clipper; clipper.PreserveCollinear = preserve_collinear; clipper.ReverseSolution = reverse_solution; if (sub.size() > 0) clipper.AddSubject(sub); - if (sub_open.size() > 0) - clipper.AddOpenSubject(sub_open); + 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; + if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open)) + return -1; // clipping bug - should never happen :) - solution = CreateCPolyTreeD(sol, 1 / scale); - if (sol_open.size() > 0) - solution_open = CreateCPathsD(sol_open, 1 / scale); - return 0; + solution = CreateCPolyTreeD(tree, 1/scale); + solution_open = CreateCPathsDFromPaths64(sol_open, 1 / scale); + return 0; //success !! } EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths, @@ -374,11 +584,11 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, if (precision < -8 || precision > 8 || !paths) return nullptr; const double scale = std::pow(10, precision); ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution); - Paths64 pp = ConvertCPathsD(paths, scale); + Paths64 pp = ConvertCPathsDToPaths64(paths, scale); clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype)); Paths64 result; clip_offset.Execute(delta * scale, result); - return CreateCPathsD(result, 1/scale); + return CreateCPathsDFromPaths64(result, 1/scale); } EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths) @@ -399,10 +609,10 @@ EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int RectD r = CRectToRect(rect); Rect64 rec = ScaleRect(r, scale); - Paths64 pp = ConvertCPathsD(paths, scale); + Paths64 pp = ConvertCPathsDToPaths64(paths, scale); class RectClip64 rc(rec); Paths64 result = rc.Execute(pp); - return CreateCPathsD(result, 1/scale); + return CreateCPathsDFromPaths64(result, 1/scale); } EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, @@ -424,347 +634,9 @@ EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, const double scale = std::pow(10, precision); Rect64 r = ScaleRect(CRectToRect(rect), scale); class RectClipLines64 rcl(r); - Paths64 pp = ConvertCPathsD(paths, scale); + Paths64 pp = ConvertCPathsDToPaths64(paths, scale); Paths64 result = rcl.Execute(pp); - return CreateCPathsD(result, 1/scale); -} - -inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2) -{ - // allocates memory for CPath64, fills in the counter, and - // returns the structure ready to be filled with path data - CPath64 result = new int64_t[2 + cnt1 *2]; - result[0] = cnt1; - result[1] = cnt2; - return result; -} - -inline CPath64 CreateCPath64(const Path64& p) -{ - // allocates memory for CPath64, fills the counter - // and returns the memory filled with path data - size_t cnt = p.size(); - if (!cnt) return nullptr; - CPath64 result = CreateCPath64(cnt, 0); - CPath64 v = result; - v += 2; // skip counters - for (const Point64& pt : p) - { - *v++ = pt.x; - *v++ = pt.y; - } - return result; -} - -inline Path64 ConvertCPath64(const CPath64& p) -{ - Path64 result; - if (p && *p) - { - CPath64 v = p; - const size_t cnt = static_cast(p[0]); - v += 2; // skip counters - result.reserve(cnt); - for (size_t i = 0; i < cnt; ++i) - { - // x,y here avoids right to left function evaluation - // result.push_back(Point64(*v++, *v++)); - int64_t x = *v++; - int64_t y = *v++; - result.push_back(Point64(x, y)); - } - } - return result; -} - -inline CPaths64 CreateCPaths64(const Paths64& pp) -{ - // allocates memory for multiple CPath64 and - // and returns this memory filled with path data - size_t cnt = pp.size(), cnt2 = cnt; - - // don't allocate space for empty paths - for (size_t i = 0; i < cnt; ++i) - if (!pp[i].size()) --cnt2; - if (!cnt2) return nullptr; - - CPaths64 result = new int64_t* [cnt2 + 1]; - CPaths64 v = result; - *v++ = CreateCPath64(0, cnt2); // assign a counter path - for (const Path64& p : pp) - { - *v = CreateCPath64(p); - if (*v) ++v; - } - return result; -} - -inline Paths64 ConvertCPaths64(const CPaths64& pp) -{ - Paths64 result; - if (pp) - { - CPaths64 v = pp; - CPath64 cnts = pp[0]; - const size_t cnt = static_cast(cnts[1]); // nb 2nd cnt - ++v; // skip cnts - result.reserve(cnt); - for (size_t i = 0; i < cnt; ++i) - result.push_back(ConvertCPath64(*v++)); - } - return result; -} - -inline CPathD CreateCPathD(size_t cnt1, size_t cnt2) -{ - // allocates memory for CPathD, fills in the counter, and - // returns the structure ready to be filled with path data - CPathD result = new double[2 + cnt1 * 2]; - result[0] = static_cast(cnt1); - result[1] = static_cast(cnt2); - return result; -} - -inline CPathD CreateCPathD(const PathD& p) -{ - // allocates memory for CPath, fills the counter - // and returns the memory fills with path data - size_t cnt = p.size(); - if (!cnt) return nullptr; - CPathD result = CreateCPathD(cnt, 0); - CPathD v = result; - v += 2; // skip counters - for (const PointD& pt : p) - { - *v++ = pt.x; - *v++ = pt.y; - } - return result; -} - -inline PathD ConvertCPathD(const CPathD& p) -{ - PathD result; - if (p) - { - CPathD v = p; - size_t cnt = static_cast(v[0]); - v += 2; // skip counters - result.reserve(cnt); - for (size_t i = 0; i < cnt; ++i) - { - // x,y here avoids right to left function evaluation - // result.push_back(PointD(*v++, *v++)); - double x = *v++; - double y = *v++; - result.push_back(PointD(x, y)); - } - } - return result; -} - -inline CPathsD CreateCPathsD(const PathsD& pp) -{ - size_t cnt = pp.size(), cnt2 = cnt; - // don't allocate space for empty paths - for (size_t i = 0; i < cnt; ++i) - if (!pp[i].size()) --cnt2; - if (!cnt2) return nullptr; - CPathsD result = new double * [cnt2 + 1]; - CPathsD v = result; - *v++ = CreateCPathD(0, cnt2); // assign counter path - for (const PathD& p : pp) - { - *v = CreateCPathD(p); - if (*v) { ++v; } - } - return result; -} - -inline PathsD ConvertCPathsD(const CPathsD& pp) -{ - PathsD result; - if (pp) - { - CPathsD v = pp; - CPathD cnts = v[0]; - size_t cnt = static_cast(cnts[1]); - ++v; // skip cnts path - result.reserve(cnt); - for (size_t i = 0; i < cnt; ++i) - result.push_back(ConvertCPathD(*v++)); - } - return result; -} - -inline Path64 ConvertCPathD(const CPathD& p, double scale) -{ - Path64 result; - if (p) - { - CPathD v = p; - size_t cnt = static_cast(*v); - v += 2; // skip counters - result.reserve(cnt); - for (size_t i = 0; i < cnt; ++i) - { - // x,y here avoids right to left function evaluation - // result.push_back(PointD(*v++, *v++)); - double x = *v++ * scale; - double y = *v++ * scale; - result.push_back(Point64(x, y)); - } - } - return result; -} - -inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale) -{ - Paths64 result; - if (pp) - { - CPathsD v = pp; - CPathD cnts = v[0]; - size_t cnt = static_cast(cnts[1]); - result.reserve(cnt); - ++v; // skip cnts path - for (size_t i = 0; i < cnt; ++i) - result.push_back(ConvertCPathD(*v++, scale)); - } - return result; -} - -inline CPathD CreateCPathD(const Path64& p, double scale) -{ - // allocates memory for CPathD, fills in the counter, and - // returns the structure filled with *scaled* path data - size_t cnt = p.size(); - if (!cnt) return nullptr; - CPathD result = CreateCPathD(cnt, 0); - CPathD v = result; - v += 2; // skip cnts - for (const Point64& pt : p) - { - *v++ = pt.x * scale; - *v++ = pt.y * scale; - } - return result; -} - -inline CPathsD CreateCPathsD(const Paths64& pp, double scale) -{ - // allocates memory for *multiple* CPathD, and - // returns the structure filled with scaled path data - size_t cnt = pp.size(), cnt2 = cnt; - // don't allocate space for empty paths - for (size_t i = 0; i < cnt; ++i) - if (!pp[i].size()) --cnt2; - if (!cnt2) return nullptr; - CPathsD result = new double* [cnt2 + 1]; - CPathsD v = result; - *v++ = CreateCPathD(0, cnt2); - for (const Path64& p : pp) - { - *v = CreateCPathD(p, scale); - if (*v) ++v; - } - return result; -} - -inline void InitCPolyPath64(CPolyTree64* cpt, - bool is_hole, const std::unique_ptr & pp) -{ - cpt->polygon = CreateCPath64(pp->Polygon()); - cpt->is_hole = is_hole; - size_t child_cnt = pp->Count(); - cpt->child_count = static_cast(child_cnt); - cpt->childs = nullptr; - if (!child_cnt) return; - cpt->childs = new CPolyPath64[child_cnt]; - CPolyPath64* child = cpt->childs; - for (const std::unique_ptr & pp_child : *pp) - InitCPolyPath64(child++, !is_hole, pp_child); -} - -inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt) -{ - CPolyTree64* result = new CPolyTree64(); - result->polygon = nullptr; - result->is_hole = false; - size_t child_cnt = pt.Count(); - result->childs = nullptr; - result->child_count = static_cast(child_cnt); - if (!child_cnt) return result; - result->childs = new CPolyPath64[child_cnt]; - CPolyPath64* child = result->childs; - for (const std::unique_ptr & pp : pt) - InitCPolyPath64(child++, true, pp); - return result; -} - -inline void DisposeCPolyPath64(CPolyPath64* cpp) -{ - if (!cpp->child_count) return; - CPolyPath64* child = cpp->childs; - for (size_t i = 0; i < cpp->child_count; ++i) - DisposeCPolyPath64(child); - delete[] cpp->childs; -} - -EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64*& cpt) -{ - if (!cpt) return; - DisposeCPolyPath64(cpt); - delete cpt; - cpt = nullptr; -} - -inline void InitCPolyPathD(CPolyTreeD* cpt, - bool is_hole, const std::unique_ptr & pp, double scale) -{ - cpt->polygon = CreateCPathD(pp->Polygon(), scale); - cpt->is_hole = is_hole; - size_t child_cnt = pp->Count(); - cpt->child_count = static_cast(child_cnt); - cpt->childs = nullptr; - if (!child_cnt) return; - cpt->childs = new CPolyPathD[child_cnt]; - CPolyPathD* child = cpt->childs; - for (const std::unique_ptr & pp_child : *pp) - InitCPolyPathD(child++, !is_hole, pp_child, scale); -} - -inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale) -{ - CPolyTreeD* result = new CPolyTreeD(); - result->polygon = nullptr; - result->is_hole = false; - size_t child_cnt = pt.Count(); - result->child_count = static_cast(child_cnt); - result->childs = nullptr; - if (!child_cnt) return result; - result->childs = new CPolyPathD[child_cnt]; - CPolyPathD* child = result->childs; - for (const std::unique_ptr & pp : pt) - InitCPolyPathD(child++, true, pp, scale); - return result; -} - -inline void DisposeCPolyPathD(CPolyPathD* cpp) -{ - if (!cpp->child_count) return; - CPolyPathD* child = cpp->childs; - for (size_t i = 0; i < cpp->child_count; ++i) - DisposeCPolyPathD(child++); - delete[] cpp->childs; -} - -EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD*& cpt) -{ - if (!cpt) return; - DisposeCPolyPathD(cpt); - delete cpt; - cpt = nullptr; + return CreateCPathsDFromPaths64(result, 1/scale); } } // end Clipper2Lib namespace diff --git a/CPP/Utils/CommonUtils.h b/CPP/Utils/CommonUtils.h index 67dbb810..c2e2d05c 100644 --- a/CPP/Utils/CommonUtils.h +++ b/CPP/Utils/CommonUtils.h @@ -13,4 +13,14 @@ Clipper2Lib::Path64 MakeRandomPoly(int width, int height, unsigned vertCnt) return result; } +Clipper2Lib::PathD MakeRandomPolyD(int width, int height, unsigned vertCnt) +{ + using namespace Clipper2Lib; + PathD result; + result.reserve(vertCnt); + for (unsigned i = 0; i < vertCnt; ++i) + result.push_back(PointD(std::rand() % width, std::rand() % height)); + return result; +} + #endif \ No newline at end of file diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 9f4a4646..7834968f 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 19 October 2023 * +* Date : 24 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -3485,7 +3485,7 @@ private bool GetIsHole() } public int Count => _childs.Count; - internal abstract PolyPathBase AddChild(Path64 p); + public abstract PolyPathBase AddChild(Path64 p); [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() @@ -3530,7 +3530,7 @@ public class PolyPath64 : PolyPathBase public PolyPath64(PolyPathBase? parent = null) : base(parent) {} [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal override PolyPathBase AddChild(Path64 p) + public override PolyPathBase AddChild(Path64 p) { PolyPathBase newChild = new PolyPath64(this); (newChild as PolyPath64)!.Polygon = p; @@ -3576,7 +3576,7 @@ public class PolyPathD : PolyPathBase public PolyPathD(PolyPathBase? parent = null) : base(parent) {} [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal override PolyPathBase AddChild(Path64 p) + public override PolyPathBase AddChild(Path64 p) { PolyPathBase newChild = new PolyPathD(this); (newChild as PolyPathD)!.Scale = Scale; @@ -3585,6 +3585,16 @@ internal override PolyPathBase AddChild(Path64 p) return newChild; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PolyPathBase AddChild(PathD p) + { + PolyPathBase newChild = new PolyPathD(this); + (newChild as PolyPathD)!.Scale = Scale; + (newChild as PolyPathD)!.Polygon = p; + _childs.Add(newChild); + return newChild; + } + [IndexerName("Child")] public PolyPathD this[int index] { diff --git a/DLL/Delphi_TestApp/Test_DLL.dpr b/DLL/Delphi_TestApp/Test_DLL.dpr index e1076bca..39e56683 100644 --- a/DLL/Delphi_TestApp/Test_DLL.dpr +++ b/DLL/Delphi_TestApp/Test_DLL.dpr @@ -13,42 +13,23 @@ uses 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; - CPath64 = ^CInt64arr; - CPath64arr = array[0..$FFFF] of CPath64; - CPaths64 = ^CPath64arr; - CDblarr = array[0..$FFFF] of Double; - CPathD = ^CDblarr; - CPathDarr = array[0..$FFFF] of CPathD; - CPathsD = ^CPathDarr; - - - // nb: Pointer sizes could be 32bit or 64 bits - // and this will depend on how the DLL was compiled - // Obviously, DLLs compiled for 64bits won't work with - // applications compiled for 32bits (and vice versa). - - PCPolyTree64 = ^CPolyTree64; - CPolyTree64 = packed record - polygon : CPath64; //pointer (32bit or 64bit) - isHole : LongBool; //32 bits - childCnt : Int32; //32 bits - childs : PCPolyTree64; //pointer (32bit or 64bit) - end; - - PCPolyTreeD = ^CPolyTreeD; - CPolyTreeD = packed record - polygon : CPathD; //pointer (32bit or 64bit) - isHole : LongBool; //32 bits - childCnt : Int32; //32 bits - childs : PCPolyTreeD; //pointer (32bit or 64bit) - end; + CInt64arr = array[0..$FFFF] of Int64; + CPath64 = ^CInt64arr; + CPaths64 = ^CInt64arr; + CPolyPath64 = ^CInt64arr; + CPolytree64 = ^CInt64arr; + + CDblarr = array[0..$FFFF] of Double; + CPathD = ^CDblarr; + CPathsD = ^CDblarr; + CPolyPathD = ^CDblarr; + CPolytreeD = ^CDblarr; const {$IFDEF WIN64} @@ -57,6 +38,7 @@ const CLIPPER2_DLL = 'Clipper2_32.dll'; {$ENDIF} + //////////////////////////////////////////////////////// // Clipper2 DLL functions //////////////////////////////////////////////////////// @@ -64,17 +46,13 @@ const function Version(): PAnsiChar; cdecl; external CLIPPER2_DLL name 'Version'; -procedure DisposeExportedCPath64(cp: CPath64); cdecl; - external CLIPPER2_DLL name 'DisposeExportedCPath64'; procedure DisposeExportedCPaths64(var cps: CPaths64); cdecl; external CLIPPER2_DLL name 'DisposeExportedCPaths64'; -procedure DisposeExportedCPathD(cp: CPathD); cdecl; - external CLIPPER2_DLL name 'DisposeExportedCPathD'; procedure DisposeExportedCPathsD(var cp: CPathsD); cdecl; external CLIPPER2_DLL name 'DisposeExportedCPathsD'; -procedure DisposeExportedCPolyTree64(var cpt: PCPolyTree64); cdecl; +procedure DisposeExportedCPolyTree64(var cpt: CPolyTree64); cdecl; external CLIPPER2_DLL name 'DisposeExportedCPolyTree64'; -procedure DisposeExportedCPolyTreeD(var cpt: PCPolyTreeD); cdecl; +procedure DisposeExportedCPolyTreeD(var cpt: CPolyTreeD); cdecl; external CLIPPER2_DLL name 'DisposeExportedCPolyTreeD'; function BooleanOp64(cliptype: UInt8; fillrule: UInt8; @@ -84,13 +62,13 @@ function BooleanOp64(cliptype: UInt8; fillrule: UInt8; preserve_collinear: boolean = true; reverse_solution: boolean = false): integer; cdecl; external CLIPPER2_DLL name 'BooleanOp64'; -function BooleanOpPt64(cliptype: UInt8; fillrule: UInt8; +function BooleanOp_PolyTree64(cliptype: UInt8; fillrule: UInt8; const subjects: CPaths64; const subjects_open: CPaths64; - const clips: CPaths64; out solution: PCPolyTree64; + const clips: CPaths64; out solution: CPolyTree64; out solution_open: CPaths64; preserve_collinear: boolean = true; reverse_solution: boolean = false): integer; cdecl; - external CLIPPER2_DLL name 'BooleanOpPt64'; + external CLIPPER2_DLL name 'BooleanOp_PolyTree64'; function BooleanOpD(cliptype: UInt8; fillrule: UInt8; const subjects: CPathsD; const subjects_open: CPathsD; @@ -99,13 +77,13 @@ function BooleanOpD(cliptype: UInt8; fillrule: UInt8; preserve_collinear: boolean = true; reverse_solution: boolean = false): integer; cdecl; external CLIPPER2_DLL name 'BooleanOpD'; -function BooleanOpPtD(cliptype: UInt8; fillrule: UInt8; +function BooleanOp_PolyTreeD(cliptype: UInt8; fillrule: UInt8; const subjects: CPathsD; const subjects_open: CPathsD; - const clips: CPathsD; out solution: PCPolyTreeD; out solution_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 'BooleanOpPtD'; + 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; @@ -130,248 +108,267 @@ 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; + magic_64 = 64; magic_D = 68; + //////////////////////////////////////////////////////// // functions related to Clipper2 DLL structures //////////////////////////////////////////////////////// -procedure DisposeLocalCPath64(cp: CPath64); +procedure DisposeLocalCPaths64(cp: CPaths64); begin FreeMem(cp); end; -procedure DisposeLocalCPaths64(var cps: CPaths64); -var - i, cnt: integer; -begin - if cps = nil then Exit; - cnt := cps[0][1]; - for i := 0 to cnt do //cnt +1 - FreeMem(cps[i]); - FreeMem(cps); - cps := nil; -end; - -procedure DisposeLocalCPathD(cp: CPathD); +procedure DisposeLocalCPathsD(cp: CPathsD); begin FreeMem(cp); end; -procedure DisposeLocalCPathsD(var cps: CPathsD); -var - i, cnt: integer; -begin - if not Assigned(cps) then Exit; - cnt := Round(cps[0][1]); - for i := 0 to cnt do //cnt +1 - FreeMem(cps[i]); - FreeMem(cps); - cps := nil; -end; - //////////////////////////////////////////////////////// -// conversion functions +// path format conversion functions //////////////////////////////////////////////////////// -function TPath64ToCPath64(const path: TPath64): CPath64; -var - i, len: integer; -begin - len := Length(path); - GetMem(Result, (2 + len * 2) * sizeof(Int64)); - Result[0] := len; - Result[1] := 0; - for i := 0 to len -1 do - begin - Result[2 + i*2] := path[i].X; - Result[3 + i*2] := path[i].Y; - end; -end; - -function TPathDToCPathD(const path: TPathD): CPathD; -var - i, len: integer; -begin - len := Length(path); - GetMem(Result, (2 + len * 2) * sizeof(Double)); - Result[0] := len; - Result[1] := 0; - for i := 0 to len -1 do - begin - Result[2 + i*2] := path[i].X; - Result[3 + i*2] := path[i].Y; - end; -end; - -function CPath64Cntrs(cnt1, cnt2: Int64): CPath64; -begin - GetMem(Result, 2 * sizeof(Int64)); - Result[0] := cnt1; - Result[1] := cnt2; -end; - -function CPathDCntrs(cnt1, cnt2: integer): CPathD; -begin - GetMem(Result, 2 * sizeof(double)); - Result[0] := cnt1; - Result[1] := cnt2; -end; - -function TPaths64ToCPaths64(const pp: TPaths64): CPaths64; +function CreateCPaths64(const pp: TPaths64): CPaths64; var i,j, len, len2: integer; + v: PInt64; begin len := Length(pp); - len2 := len; + len2 := 2; for i := 0 to len -1 do - if Length(pp[i]) = 0 then - dec(len2); - GetMem(Result, (1 + len2) * sizeof(Pointer)); - Result[0] := CPath64Cntrs(0, len2); // add the counter 'path' - j := 1; + if Length(pp[i]) > 0 then + inc(len2, Length(pp[i]) *2 + 2); + GetMem(Result, len2 * sizeof(Int64)); + Result[0] := 0; + Result[1] := len; + v := @Result[2]; for i := 0 to len -1 do begin - if Length(pp[i]) = 0 then continue; - Result[j] := TPath64ToCPath64(pp[i]); - inc(j); + 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 TPathsDToCPathsD(const pp: TPathsD): CPathsD; +function CreateCPathsD(const pp: TPathsD): CPathsD; var i,j, len, len2: integer; + v: PDouble; begin len := Length(pp); - len2 := len; + len2 := 2; for i := 0 to len -1 do - if Length(pp[i]) = 0 then - dec(len2); - - GetMem(Result, (1 + len2) * sizeof(Pointer)); - Result[0] := CPathDCntrs(0, len2); // add the counter 'path' - j := 1; + if Length(pp[i]) > 0 then + inc(len2, Length(pp[i]) *2 + 2); + GetMem(Result, len2 * sizeof(double)); + Result[0] := 0; + Result[1] := len; + v := @Result[2]; for i := 0 to len -1 do begin - if Length(pp[i]) = 0 then continue; - Result[j] := TPathDToCPathD(pp[i]); - inc(j); + 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 CPath64ToPath64(cp: CPath64): TPath64; +function ConvertToTPaths64(cp: CPaths64): TPaths64; var - i: integer; - cnt: Int64; + i, j, len, len2: integer; + v: PInt64; begin - if not Assigned(cp) then - cnt := 0 else - cnt := cp[0]; - SetLength(Result, cnt); - for i := 0 to cnt -1 do + Result := nil; + v := PInt64(cp); + if v^ <> 0 then Exit; inc(v); + len := v^; inc(v); + SetLength(Result, len); + for i := 0 to len -1 do begin - Result[i].X := cp[2 + i*2]; - Result[i].Y := cp[3 + i*2]; + 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 CPathDToPathD(cp: CPathD): TPathD; +function ConvertToTPathsD(cp: CPathsD): TPathsD; var - i, cnt: integer; + i, j, len, len2: integer; + v: PDouble; begin - if not Assigned(cp) then - cnt := 0 else - cnt := Round(cp[0]); - SetLength(Result, cnt); - for i := 0 to cnt -1 do + Result := nil; + if cp[0] <> 0 then Exit; + len := Round(cp[1]); + SetLength(Result, len); + v := @cp[2]; + for i := 0 to len -1 do begin - Result[i].X := cp[2 + i*2]; - Result[i].Y := cp[3 + i*2]; + 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 CPaths64ToPaths64(cps: CPaths64): TPaths64; +function GetPolyPath64ArrayLen(const pp: TPolyPath64): integer; var i: integer; - cnt: Int64; begin - if not Assigned(cps) then - cnt := 0 else - cnt := cps[0][1]; - SetLength(Result, cnt); - for i := 1 to cnt do - Result[i-1] := CPath64ToPath64(cps[i]); + Result := 4; // magic + is_hole + child_count + poly_length + inc(Result, Length(pp.Polygon) * 2); + for i := 0 to pp.Count -1 do + Inc(Result, GetPolyPath64ArrayLen(pp.Child[i])); end; -function CPathsDToPathsD(cps: CPathsD): TPathsD; -var - i, cnt: integer; +procedure GetPolytreeCountAndCStorageSize(const tree: TPolyTree64; + out cnt: integer; out arrayLen: integer); begin - if not Assigned(cps) then - cnt := 0 else - cnt := Round(cps[0][1]); - SetLength(Result, cnt); - for i := 1 to cnt do - Result[i-1] := CPathDToPathD(cps[i]); + cnt := tree.Count; // nb: top level count only + arrayLen := GetPolyPath64ArrayLen(tree); end; -procedure CPt64Internal(cpt: PCPolyTree64; var paths: TPaths64); +procedure CreateCPolyPathD(const pp: TPolyPath64; + var v: PDouble; scale: double); var - i: integer; - child: PCPolyTree64; + i, len: integer; begin - if Assigned(cpt.polygon) then - AppendPath(paths, CPath64ToPath64(cpt.polygon)); - if cpt.childCnt = 0 then Exit; - child := cpt.childs; - for i := 0 to cpt.childCnt -1 do + v^ := magic_64; inc(v); + if pp.IsHole then + v^ := 1 else + v^ := 0; + inc(v); + v^ := pp.Count; inc(v); + len := Length(pp.Polygon); + v^ := len; inc(v); + for i := 0 to len -1 do begin - CPt64Internal(child, paths); - inc(child); + 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 CPolytree64ToPaths64(cpt: PCPolyTree64): TPaths64; + +function CreateCPolyTreeD(const tree: TPolyTree64; scale: double): CPolyTreeD; var - i: integer; - child: PCPolyTree64; + i, cnt, arrayLen: integer; + v: PDouble; begin Result := nil; - if not Assigned(cpt) or (cpt.childCnt = 0) then Exit; - child := cpt.childs; - for i := 0 to cpt.childCnt -1 do + GetPolytreeCountAndCStorageSize(tree, cnt, arrayLen); + if cnt = 0 then Exit; + // allocate storage + GetMem(Result, arrayLen * SizeOf(double)); + + v := PDouble(Result); + v^ := magic_64; inc(v); + v^ := 0; inc(v); + v^ := tree.Count; inc(v); + v^ := 0; 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, magic, childCount, len: integer; + path: TPath64; + newOwner: TPolyPath64; +begin + Result := false; + magic := v^; inc(v, 2); + childCount := v^; inc(v); + len := v^; inc(v); + if (magic <> magic_64) or (len = 0) then Exit; + SetLength(path, len); + for i := 0 to len -1 do begin - CPt64Internal(child, Result); - inc(child); + 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; -procedure CPtDInternal(cpt: PCPolyTreeD; var paths: TPathsD); +function BuildPolyTree64FromCPolyTree(tree: CPolyTree64; outTree: TPolyTree64): Boolean; var - i: integer; - child: PCPolyTreeD; + v: PInt64; + i, magic, childCount, len: integer; begin - AppendPath(paths, CPathDToPathD(cpt.polygon)); - if cpt.childCnt = 0 then Exit; - child := cpt.childs; - for i := 0 to cpt.childCnt -1 do - begin - CPtDInternal(child, paths); - inc(child); - end; + Result := false; + outTree.Clear(); + v := PInt64(tree); + magic := v^; inc(v, 2); + childCount := v^; inc(v); + len := v^; inc(v); + if (magic <> magic_64) or (len > 0) then Exit; + for i := 0 to childCount -1 do + if not CreatePolyPath64FromCPolyPath(v, outTree) then Exit; + Result := true; end; -function CPolytreeDToPathsD(cpt: PCPolyTreeD): TPathsD; +function CreatePolyPathDFromCPolyPath(var v: PDouble; owner: TPolyPathD): Boolean; var - i: integer; - child: PCPolyTreeD; + i, magic, childCount, len: integer; + path: TPathD; + newOwner: TPolyPathD; begin - Result := nil; - if not Assigned(cpt) or (cpt.childCnt = 0) then Exit; - child := cpt.childs; - for i := 0 to cpt.childCnt -1 do + Result := false; + magic := Round(v^); inc(v, 2); + childCount := Round(v^); inc(v); + len := Round(v^); inc(v); + if (magic <> magic_64) or (len = 0) then Exit; + SetLength(path, len); + for i := 0 to len -1 do begin - CPtDInternal(child, Result); - inc(child); + 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, magic, childCount, len: integer; +begin + Result := false; + outTree.Clear(); + v := PDouble(tree); + magic := Round(v^); inc(v, 2); + childCount := Round(v^); inc(v); + len := Round(v^); inc(v); + if (magic <> magic_64) or (len > 0) then Exit; + for i := 0 to childCount -1 do + if not CreatePolyPathDFromCPolyPath(v, outTree) then Exit; + Result := true; end; //////////////////////////////////////////////////////// @@ -430,140 +427,14 @@ begin end; end; -procedure WriteCPath64(p: CPath64); -var - i, len: integer; - s: string; -begin - len := p[0]; - if len = 0 then Exit; - s := ''; - for i := 0 to len -1 do - s := s + - inttostr(p[2 + i*2]) + ',' + - inttostr(p[3 + i*2]) + ', '; - WriteLn(s); -end; - -procedure WriteCPaths64(p: CPaths64); -var - i, len: integer; -begin - len := p[0][1]; - for i := 1 to len do - WriteCPath64(p[i]); - WriteLn(''); -end; - -procedure WritePath64(p: TPath64); -var - i,len: integer; - s: string; -begin - len := Length(p); - if len = 0 then Exit; - s := ''; - for i := 0 to len -1 do - s := s + - inttostr(p[i].X) + ',' + - inttostr(p[i].Y) + ', '; - WriteLn(s); -end; - - -procedure WritePaths64(pp: TPaths64); -var - i: integer; -begin - for i := 0 to High(pp) do - WritePath64(pp[i]); -end; - -procedure WritePathD(p: TPathD); -var - i,len: integer; - s: string; -begin - len := Length(p); - if len = 0 then Exit; - s := ''; - for i := 0 to len -1 do - s := Format('%s%1.2f,%1.2f, ', - [s, p[i].X, p[i].Y]); - WriteLn(s); -end; - -procedure WritePathsD(pp: TPathsD); -var - i: integer; -begin - for i := 0 to High(pp) do - WritePathD(pp[i]); -end; - -procedure WriteCPolyTree64(pp: PCPolyTree64); -var - i: integer; - child: PCPolyTree64; -begin - if Assigned(pp.polygon) then - WriteCPath64(pp.polygon); - if pp.childCnt = 0 then Exit; - child := pp.childs; - for i := 0 to pp.childCnt -1 do - begin - WriteCPolyTree64(child); - inc(child); - end; -end; - -procedure WriteCPathD(p: CPathD); -var - i, len: integer; - s: string; -begin - len := round(p[0]); - if len = 0 then Exit; - s := ''; - for i := 0 to len -1 do - s := Format('%s%1.2f,%1.2f, ', - [s, p[2 + i*2], p[3 + i*2]]); - WriteLn(s); -end; - -procedure WriteCPathsD(pp: CPathsD); -var - i, len: integer; -begin - len := Round(pp[0][1]); - for i := 1 to len do - WriteCPathD(pp[i]); -end; - -procedure WriteCPolyTreeD(pp: PCPolyTreeD); -var - i: integer; - child: PCPolyTreeD; -begin - if Assigned(pp.polygon) then - WriteCPathD(pp.polygon); - if pp.childCnt = 0 then Exit; - child := pp.childs; - for i := 0 to pp.childCnt -1 do - begin - WriteCPolyTreeD(child); - inc(child); - end; -end; - procedure ShowSvgImage(const svgFilename: string); begin ShellExecute(0, 'open',PChar(svgFilename), nil, nil, SW_SHOW); end; const - displayWidth = 800; - displayHeight = 600; + displayWidth = 600; + displayHeight = 400; procedure DisplaySVG(const sub, subo, clp, sol, solo: TPathsD; const svgName: string; width: integer = displayWidth; @@ -596,6 +467,7 @@ begin AddSubject(svg, sub); AddOpenSubject(svg, subo); AddClip(svg, clp); + AddSolution(svg, sol); AddOpenSolution(svg, solo); SaveSvg(svg, svgName, width, height); @@ -609,39 +481,6 @@ end; // test procedures //////////////////////////////////////////////////////// -procedure Test_RandIntersect_MegaStress(maxCnt: integer); -var - i: integer; - sub, clp: TPathsD; - csub_local, cclp_local: CPathsD; - csol_extern, csolo_extern: CPathsD; -begin - csol_extern := nil; - csolo_extern := nil; - SetLength(sub, 1); - SetLength(clp, 1); - - for i := 1 to maxCnt do - begin - sub[0] := MakeRandomPathD(displayWidth, displayHeight, 50); - clp[0] := MakeRandomPathD(displayWidth, displayHeight, 50); - csub_local := TPathsDToCPathsD(sub); - cclp_local := TPathsDToCPathsD(clp); - - BooleanOpD(Uint8(TClipType.ctIntersection), - Uint8(TFillRule.frNonZero), - csub_local, nil, cclp_local, - csol_extern, csolo_extern); - - DisposeLocalCPathsD(csub_local); - DisposeLocalCPathsD(cclp_local); - DisposeExportedCPathsD(csol_extern); - DisposeExportedCPathsD(csolo_extern); - if i mod 100 = 0 then Write('.'); - end; - WriteLn(#10'Passed!'); -end; - procedure Test_Version(); begin Write(#10'Clipper2 DLL version: '); @@ -662,20 +501,16 @@ begin SetLength(clp, 1); clp[0] := MakeRandomPath(displayWidth, displayHeight, edgeCnt); // convert paths into DLL structures (will require local clean up) - csub_local := TPaths64ToCPaths64(sub); - cclp_local := TPaths64ToCPaths64(clp); + csub_local := CreateCPaths64(sub); + cclp_local := CreateCPaths64(clp); // do the DLL operation - BooleanOp64(Uint8(TClipType.ctIntersection), - Uint8(TFillRule.frNonZero), + BooleanOp64(Intersection, NonZero, csub_local, nil, cclp_local, csol_extern, csolo_extern); - // optionally display result on the console - //WriteCPaths64(csol_extern); - DisplaySVG(sub, nil, clp, - CPaths64ToPaths64(csol_extern), nil, 'BooleanOp64.svg'); + ConvertToTPaths64(csol_extern), nil, 'BooleanOp64.svg'); // clean up DisposeLocalCPaths64(csub_local); @@ -698,8 +533,8 @@ begin SetLength(clp, 1); clp[0] := MakeRandomPathD(displayWidth, displayHeight, edgeCnt); // convert paths into DLL structures (will require local clean up) - csub_local := TPathsDToCPathsD(sub); - cclp_local := TPathsDToCPathsD(clp); + csub_local := CreateCPathsD(sub); + cclp_local := CreateCPathsD(clp); // do the DLL operation BooleanOpD(Uint8(TClipType.ctIntersection), @@ -711,7 +546,7 @@ begin //WriteCPaths64(csol_extern); DisplaySVG(sub, nil, clp, - CPathsDToPathsD(csol_extern), nil, 'BooleanOpD.svg'); + ConvertToTPathsD(csol_extern), nil, 'BooleanOpD.svg'); DisposeLocalCPathsD(csub_local); DisposeLocalCPathsD(cclp_local); @@ -719,42 +554,82 @@ begin DisposeExportedCPathsD(csolo_extern); end; -procedure Test_BooleanOpPtD(edgeCnt: integer); +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; + DisposeExportedCPolyTree64(csol_extern); + DisposeExportedCPaths64(csol_open_extern); + + DisposeLocalCPaths64(csub_local); + DisposeLocalCPaths64(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: PCPolyTreeD; - csolo_extern: CPathsD; + csol_extern: CPolyTreeD; + tree: TPolyTreeD; + csol_open_extern: CPathsD; begin // setup - csolo_extern := nil; - WriteLn(#10'Testing BooleanOpPtD'); + 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 := TPathsDToCPathsD(sub); - cclp_local := TPathsDToCPathsD(clp); + csub_local := CreateCPathsD(sub); + cclp_local := CreateCPathsD(clp); // do the DLL operation - BooleanOpPtD(Uint8(TClipType.ctIntersection), - Uint8(TFillRule.frNonZero), - csub_local, nil, cclp_local, - csol_extern, csolo_extern); - - // optionally display result on the console - //WriteCPaths64(csol_extern); - - sol := CPolytreeDToPathsD(csol_extern); + 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; DisposeExportedCPolyTreeD(csol_extern); - DisposeExportedCPathsD(csolo_extern); + DisposeExportedCPathsD(csol_open_extern); DisposeLocalCPathsD(csub_local); DisposeLocalCPathsD(cclp_local); // finally, display and clean up - DisplaySVG(sub, nil, clp, sol, nil, 'BooleanOpPtD.svg'); + DisplaySVG(sub, nil, clp, sol, nil, 'BooleanOp_PolyTreeD.svg'); end; procedure Test_InflatePathsD(edgeCnt: integer; delta: double); @@ -769,7 +644,7 @@ begin SetLength(sub, 1); sub[0] := MakeRandomPathD(displayWidth, displayHeight, edgeCnt); // convert path into required DLL structure (also requires local clean up) - csub_local := TPathsDToCPathsD(sub); + csub_local := CreateCPathsD(sub); // and because offsetting self-intersecting paths is unpredictable // we must remove self-intersection via a union operation @@ -785,31 +660,13 @@ begin //WriteCPaths64(csol_extern); DisplaySVG(sub, nil, nil, - CPathsDToPathsD(csol_extern), nil, 'InflatePaths64.svg'); + ConvertToTPathsD(csol_extern), nil, 'InflatePathsD.svg'); DisposeLocalCPathsD(csub_local); DisposeExportedCPathsD(csol_extern); DisposeExportedCPathsD(csolo_extern); end; -function RotatePath(const path: TPathD; - const focalPoint: TPointD; angle: double): TPathD; -var - i: integer; - sinA, cosA, x,y: double; -begin - SetLength(Result, length(path)); - SinCos(angle, sinA, cosA); - for i := 0 to high(path) do - begin - x := path[i].X - focalPoint.X; - y := path[i].Y - focalPoint.Y; - Result[i].X := x * cosA - y * sinA + focalPoint.X; - Result[i].Y := x * sinA + y * cosA + focalPoint.Y; - end; -end; - - procedure Test_RectClipD(shapeCount: integer); var i, rec_margin: Integer; @@ -853,20 +710,18 @@ begin Random(Round(maxOffX)), Random(Round(maxOffY))); end; - csub_local := TPathsDToCPathsD(sub); + csub_local := CreateCPathsD(sub); csol_extern := RectClipD(rec, csub_local, 2, true); - sol1 := CPathsDToPathsD(csol_extern); + sol1 := ConvertToTPathsD(csol_extern); DisposeExportedCPathsD(csol_extern); // do the DLL operation again with ConvexOnly disabled csol_extern := RectClipD(rec, csub_local, 2, false); - sol2 := CPathsDToPathsD(csol_extern); + sol2 := ConvertToTPathsD(csol_extern); SetLength(clp, 1); clp[0] := rec.AsPath; - //DisplaySVG(sub, nil, clp, nil, nil, 'RectClip64_1.svg', w,h); - //DisplaySVG(sub, nil, clp, sol1, nil, 'RectClip64_2.svg', w,h); DisplaySVG(sub, nil, clp, sol2, nil, 'RectClip64_3.svg', w,h); DisposeLocalCPathsD(csub_local); @@ -885,7 +740,7 @@ begin SetLength(sub, 1); sub[0] := MakeRandomPath(displayWidth, displayHeight, edgeCnt); - csub_local := TPaths64ToCPaths64(sub); + csub_local := CreateCPaths64(sub); rec.Left := 80; rec.Top := 80; @@ -895,71 +750,16 @@ begin // do the DLL operation csolo_extern := RectClipLines64(rec, csub_local); - // optionally display result on the console - //WriteCPaths64(csol_extern); - - // finally, display and clean up - SetLength(clp, 1); clp[0] := rec.AsPath; DisplaySVG(nil, sub, clp, nil, - CPaths64ToPaths64(csolo_extern), 'RectClipLines64.svg'); + ConvertToTPaths64(csolo_extern), 'RectClipLines64.svg'); DisposeLocalCPaths64(csub_local); DisposeExportedCPaths64(csolo_extern); end; -procedure Test_Performance(lowThousand, hiThousand: integer); -var - i: integer; - tr: TTimeRec; - sub, clp: TPaths64; - csub_local, cclp_local: CPaths64; - csol_extern, csolo_extern: CPaths64; -const - w = 800; - h = 600; -begin - csolo_extern := nil; - WriteLn(#10'Testing Performance'); - for i := lowThousand to hiThousand do - begin - Write(format(#10' C++ DLL - %d edges: ', [i*1000])); - // setup - SetLength(sub, 1); - sub[0] := MakeRandomPath(w, h, i*1000); - SetLength(clp, 1); - clp[0] := MakeRandomPath(w, h, i*1000); - // convert paths into DLL structures (will require local clean up) - csub_local := TPaths64ToCPaths64(sub); - cclp_local := TPaths64ToCPaths64(clp); - - StartTimer(tr); - // do the DLL operation - BooleanOp64(Uint8(TClipType.ctIntersection), - Uint8(TFillRule.frNonZero), - csub_local, nil, cclp_local, - csol_extern, csolo_extern); - WriteLn(format('%1.3n secs', [EndTimer(tr)])); - - Write(format(' Pure delphi - %d edges: ', [i*1000])); - StartTimer(tr); - Clipper.Intersect(sub, clp, Clipper.frNonZero); - WriteLn(format('%1.3n secs', [EndTimer(tr)])); - - if i = hiThousand then - DisplaySVG(sub, nil, clp, CPaths64ToPaths64(csol_extern), nil, 'Performance.svg'); - - DisposeLocalCPaths64(csub_local); - DisposeLocalCPaths64(cclp_local); - DisposeExportedCPaths64(csol_extern); - DisposeExportedCPaths64(csolo_extern); - - end; //bottom of for loop -end; - - //////////////////////////////////////////////////////// // main entry here //////////////////////////////////////////////////////// @@ -969,14 +769,13 @@ var begin Randomize; Test_Version(); - Test_BooleanOp64(50); - Test_BooleanOpD(75); - Test_BooleanOpPtD(20); + Test_BooleanOp64(25); + Test_BooleanOpD(25); + Test_BooleanOp_Polytree64(15); + Test_BooleanOp_PolytreeD(25); Test_InflatePathsD(20, -10); // edgeCount, offsetDist Test_RectClipD(15); Test_RectClipLines64(25); - Test_Performance(1, 5); // 1000 to 5000 - Test_RandIntersect_MegaStress(10000); WriteLn(#10'Press Enter to quit.'); ReadLn(s); diff --git a/Delphi/Clipper2Lib/Clipper.Core.pas b/Delphi/Clipper2Lib/Clipper.Core.pas index 4e710bd8..737cb383 100644 --- a/Delphi/Clipper2Lib/Clipper.Core.pas +++ b/Delphi/Clipper2Lib/Clipper.Core.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 17 July 2023 * +* Date : 24 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library module * diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index 416a18aa..db5d6f85 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 19 October 2023 * +* Date : 24 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -347,10 +347,9 @@ TPolyPathBase = class TPolyPath64 = class(TPolyPathBase) {$IFDEF STRICT}strict{$ENDIF} private FPath : TPath64; - function GetChild64(index: Integer): TPolyPath64; - protected - function AddChild(const path: TPath64): TPolyPathBase; override; + function GetChild64(index: Integer): TPolyPath64; public + function AddChild(const path: TPath64): TPolyPathBase; override; property Child[index: Integer]: TPolyPath64 read GetChild64; default; property Polygon: TPath64 read FPath; end; @@ -403,8 +402,9 @@ TPolyPathD = class(TPolyPathBase) function GetChildD(index: Integer): TPolyPathD; protected FScale : double; - function AddChild(const path: TPath64): TPolyPathBase; override; public + function AddChild(const path: TPath64): TPolyPathBase; overload; override; + function AddChild(const path: TPathD): TPolyPathBase; reintroduce; overload; property Polygon: TPathD read FPath; property Child[index: Integer]: TPolyPathD read GetChildD; default; end; @@ -4252,6 +4252,16 @@ function TPolyPathD.AddChild(const path: TPath64): TPolyPathBase; end; //------------------------------------------------------------------------------ +function TPolyPathD.AddChild(const path: TPathD): TPolyPathBase; +begin + Result := TPolyPathD.Create; + Result.Parent := self; + TPolyPathD(Result).fScale := fScale; + TPolyPathD(Result).FPath := path; + ChildList.Add(Result); +end; +//------------------------------------------------------------------------------ + function TPolyPathD.GetChildD(index: Integer): TPolyPathD; begin Result := TPolyPathD(GetChild(index)); From 11c97a14ba55dc1210c2a9714105b398fda73bb2 Mon Sep 17 00:00:00 2001 From: angusj Date: Wed, 25 Oct 2023 16:39:53 +1000 Subject: [PATCH 59/95] Minor bugfix in clipper.export.h Added clipper.export.h CI test --- CPP/CMakeLists.txt | 1 + .../include/clipper2/clipper.engine.h | 10 +- .../include/clipper2/clipper.export.h | 35 +-- CPP/Tests/TestExportHeaders.cpp | 222 ++++++++++++++++++ 4 files changed, 241 insertions(+), 27 deletions(-) create mode 100644 CPP/Tests/TestExportHeaders.cpp diff --git a/CPP/CMakeLists.txt b/CPP/CMakeLists.txt index 257079ea..b7106d4a 100644 --- a/CPP/CMakeLists.txt +++ b/CPP/CMakeLists.txt @@ -193,6 +193,7 @@ else() set_target_properties(gtest gtest_main PROPERTIES FOLDER GTest) endif() set(ClipperTests_SRC + Tests/TestExportHeaders.cpp Tests/TestLines.cpp Tests/TestOffsets.cpp Tests/TestOffsetOrientation.cpp diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h index 137f021e..e41fe633 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 24 October 2023 * +* Date : 25 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -344,12 +344,12 @@ namespace Clipper2Lib { childs_.resize(0); } - const PolyPath64* operator [] (size_t index) const + PolyPath64* operator [] (size_t index) const { return childs_[index].get(); //std::unique_ptr } - const PolyPath64* Child(size_t index) const + PolyPath64* Child(size_t index) const { return childs_[index].get(); } @@ -401,12 +401,12 @@ namespace Clipper2Lib { childs_.resize(0); } - const PolyPathD* operator [] (size_t index) const + PolyPathD* operator [] (size_t index) const { return childs_[index].get(); } - const PolyPathD* Child(size_t index) const + PolyPathD* Child(size_t index) const { return childs_[index].get(); } diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index 748cb21c..49f4bcc9 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 24 October 2023 * +* Date : 25 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module exports the Clipper2 Library (ie DLL/so) * @@ -106,13 +106,13 @@ inline Rect CRectToRect(const CRect& rect) EXTERN_DLL_EXPORT const char* Version(); -// Some exported functions will return data in structures that -// have been allocated in heap memory. Eventually this memory will +// Most of the exported functions below return data in structures that +// has been allocated in heap memory. Eventually this memory will // need to be released using one of the following 'DisposeExported...' // functions. (This may be the only safe way to release this memory // since the executable accessing these exported functions may use -// a memory manager that allocates and releases heap memory in a -// different way. +// a different memory manager (MM)., And allocating memory using one +// MM and releasing the same memory in another will cause problems.) EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& p) { delete[] p; @@ -184,23 +184,13 @@ EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, // INTERNAL FUNCTIONS ////////////////////////////////////////////////////// -static void GetPathCountAndCPaths64ArrayLen(const Paths64& paths, size_t& cnt, size_t& array_len) -{ - array_len = 2; - cnt = 0; - for (const Path64& path : paths) - if (path.size()) - { - array_len += path.size() * 2 + 2; - ++cnt; - } -} - -static void GetPathCountAndCPathsDArrayLen(const PathsD& paths, size_t& cnt, size_t& array_len) +template +static void GetPathCountAndCPathsArrayLen(const std::vector < std::vector >>& paths, +size_t& cnt, size_t& array_len) { array_len = 2; cnt = 0; - for (const PathD& path : paths) + for (const Path& path : paths) if (path.size()) { array_len += path.size() * 2 + 2; @@ -234,7 +224,7 @@ static size_t GetPolyPathDArrayLen(const PolyPathD& pp) static CPaths64 CreateCPaths64(const Paths64& paths) { size_t cnt, array_len; - GetPathCountAndCPaths64ArrayLen(paths, cnt, array_len); + GetPathCountAndCPathsArrayLen(paths, cnt, array_len); int64_t* result = new int64_t[array_len], * v = result; *v++ = 0; *v++ = cnt; @@ -255,7 +245,7 @@ static CPaths64 CreateCPaths64(const Paths64& paths) static CPathsD CreateCPathsD(const PathsD& paths) { size_t cnt, array_len; - GetPathCountAndCPathsDArrayLen(paths, cnt, array_len); + GetPathCountAndCPathsArrayLen(paths, cnt, array_len); double* result = new double[array_len], * v = result; *v++ = 0; *v++ = (double)cnt; @@ -277,7 +267,7 @@ CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale) { if (!paths.size()) return nullptr; size_t cnt, array_len; - GetPathCountAndCPaths64ArrayLen(paths, cnt, array_len); + GetPathCountAndCPathsArrayLen(paths, cnt, array_len); CPathsD result = new double[array_len], v = result; *v++ = 0; *v++ = (double)cnt; @@ -331,6 +321,7 @@ static PathsD ConvertCPathsD(const CPathsD paths) v += 2; PathD path; path.reserve(cnt2); + for (size_t j = 0; j < cnt2; ++j) { double x = *v++, y = *v++; path.push_back(PointD(x, y)); diff --git a/CPP/Tests/TestExportHeaders.cpp b/CPP/Tests/TestExportHeaders.cpp new file mode 100644 index 00000000..af761573 --- /dev/null +++ b/CPP/Tests/TestExportHeaders.cpp @@ -0,0 +1,222 @@ +#include + +#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 magic = *v++, hole = *v++, child_count = *v++, poly_len = *v++; + if (magic != magic_64 || !poly_len) return false; + Path64 path; + path.reserve(poly_len); + for (size_t i = 0; i < poly_len; ++i) + { + int64_t x = *v++, y = *v++; + path.push_back(Point64(x,y)); + } + + 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(); + int64_t* v = tree; + int64_t magic = *v++, hole = *v++, child_count = *v++, poly_len = *v++; + if (magic != magic_64 || poly_len) return false; + for (size_t i = 0; i < child_count; ++i) + if (!CreatePolyPath64FromCPolyPath(v, result)) return false; + return true; +} + +static bool CreatePolyPathDFromCPolyPath(CPolyPathD& v, PolyPathD& owner) +{ + int64_t magic = *v++, hole = *v++, child_count = *v++, poly_len = *v++; + if (magic != magic_64 || !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++; + path.push_back(PointD(x, y)); + } + 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 magic = *v++, hole = *v++, child_count = *v++, poly_len = *v++; + if (magic != magic_64 || poly_len) return false; + 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; + uint8_t EvenOdd = 0, NonZero = 1, Positive = 2, Negative = 3; + + Paths64 subj, clip, solution; + //subj.push_back(MakeRandomPoly(600, 400, 25)); + //clip.push_back(MakeRandomPoly(600, 400, 25)); + + for (int i = 1; i < 6; ++i) + subj.push_back(MakePath({ -i*20,-i * 20, i * 20,-i * 20, i * 20,i * 20, -i * 20,i * 20 })); + clip.push_back(MakePath({ -90,-120,90,-120, 90,120, -90,120 })); + + CPaths64 c_subj_open = nullptr, c_sol = nullptr, c_sol_open = nullptr; + + // Note: while CreateCPaths64 isn't exported in clipper.export.h, it can still + // be used here because we're simply statically compiling clipper.export.h. + // 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 = CreateCPaths64(subj); + CPaths64 c_clip = CreateCPaths64(clip); + + BooleanOp64(Intersection, EvenOdd, c_subj, c_subj_open, c_clip, c_sol, c_sol_open); + solution = ConvertCPaths64(c_sol); + + //clean up !!! + delete[] c_subj; + delete[] c_clip; + DisposeExportedCPaths64(c_sol); + DisposeExportedCPaths64(c_sol_open); + + EXPECT_EQ(solution.size(), 5); +} + +TEST(Clipper2Tests, ExportHeaderD) +{ + + uint8_t None = 0, Intersection = 1, Union = 2, Difference = 3, Xor = 4; + uint8_t EvenOdd = 0, NonZero = 1, Positive = 2, Negative = 3; + + PathsD subj, clip, solution; + //subj.push_back(MakeRandomPolyD(600, 400, 25)); + //clip.push_back(MakeRandomPolyD(600, 400, 25)); + for (int i = 1; i < 6; ++i) + subj.push_back(MakePathD({ -i * 20,-i * 20, i * 20,-i * 20, i * 20,i * 20, -i * 20,i * 20 })); + clip.push_back(MakePathD({ -90,-120,90,-120, 90,120, -90,120 })); + CPathsD c_subj_open = nullptr, c_sol = nullptr, c_sol_open = nullptr; + + // Note: while CreateCPathsD isn't exported in clipper.export.h, it can still + // be used here because we're simply statically compiling clipper.export.h. + // 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 = CreateCPathsD(subj); + CPathsD c_clip = CreateCPathsD(clip); + + BooleanOpD(Intersection, EvenOdd, c_subj, c_subj_open, c_clip, c_sol, c_sol_open); + solution = ConvertCPathsD(c_sol); + + //clean up !!! + delete[] c_subj; + delete[] c_clip; + DisposeExportedCPathsD(c_sol); + DisposeExportedCPathsD(c_sol_open); + + EXPECT_EQ(solution.size(), 5); +} + +TEST(Clipper2Tests, ExportHeaderTree64) +{ + uint8_t None = 0, Intersection = 1, Union = 2, Difference = 3, Xor = 4; + uint8_t EvenOdd = 0, NonZero = 1, Positive = 2, Negative = 3; + + Paths64 subj, clip, solution; + for (int i = 1; i < 6; ++i) + subj.push_back(MakePath({ -i * 20,-i * 20, i * 20,-i * 20, i * 20,i * 20, -i * 20,i * 20 })); + clip.push_back(MakePath({ -90,-120,90,-120, 90,120, -90,120 })); + CPaths64 c_subj_open = nullptr, c_sol = nullptr, c_sol_open = nullptr; + + // Note: while CreateCPaths64 isn't exported in clipper.export.h, it can still + // be used here because we're statically compiling clipper.export.h. + // 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 = CreateCPaths64(subj); + CPaths64 c_clip = CreateCPaths64(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; + + // convert CPolyTree64 to PolyTree64 + BuildPolyTree64FromCPolyTree(c_sol_tree, sol_tree); + + // convert PolyTree64 to Paths64 + solution = PolyTreeToPaths64(sol_tree); + + //clean up !!! + delete[] c_subj; + delete[] c_clip; + DisposeExportedCPolyTree64(c_sol_tree); + DisposeExportedCPaths64(c_sol_open); + + PolyPath64* pp = &sol_tree; + for (int i = 0; i < 4; ++i) + { + EXPECT_TRUE(pp->Count() == 1); pp = pp->Child(0); + } + +} + + +TEST(Clipper2Tests, ExportHeaderTreeD) +{ + uint8_t None = 0, Intersection = 1, Union = 2, Difference = 3, Xor = 4; + uint8_t EvenOdd = 0, NonZero = 1, Positive = 2, Negative = 3; + + PathsD subj, clip, solution; + for (int i = 1; i < 6; ++i) + subj.push_back(MakePathD({ -i * 20,-i * 20, i * 20,-i * 20, i * 20,i * 20, -i * 20,i * 20 })); + clip.push_back(MakePathD({ -90,-120,90,-120, 90,120, -90,120 })); + CPathsD c_subj_open = nullptr, c_sol = nullptr, c_sol_open = nullptr; + + // Note: while CreateCPathsD isn't exported in clipper.export.h, it can still + // be used here because we're statically compiling clipper.export.h. + // 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 = CreateCPathsD(subj); + CPathsD c_clip = CreateCPathsD(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); + + PolyTreeD sol_tree; + // convert CPolyTreeD to PolyTreeD + BuildPolyTreeDFromCPolyTree(c_sol_tree, sol_tree); + // convert PolyTreeD to PathsD + solution = PolyTreeToPathsD(sol_tree); + + //clean up !!! + delete[] c_subj; + delete[] c_clip; + DisposeExportedCPolyTreeD(c_sol_tree); + DisposeExportedCPathsD(c_sol_open); + + PolyPathD* pp = &sol_tree; + for (int i = 0; i < 4; ++i) + { + EXPECT_TRUE(pp->Count() == 1); pp = pp->Child(0); + } +} From 266a706084c34b849be30a4806fb2b292eb99bbd Mon Sep 17 00:00:00 2001 From: angusj Date: Wed, 25 Oct 2023 17:08:20 +1000 Subject: [PATCH 60/95] clipper.export.h compile error --- CPP/Clipper2Lib/include/clipper2/clipper.export.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index 49f4bcc9..6ebac1d3 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -98,7 +98,12 @@ inline Rect CRectToRect(const CRect& rect) return result; } -#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport) +#ifdef _WIN32 + #define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport) +#else + #define EXTERN_DLL_EXPORT extern "C" +#endif + ////////////////////////////////////////////////////// // EXPORTED FUNCTION DECLARATIONS From 23105245c79afcef9d4872e15b669f073ffb0d3c Mon Sep 17 00:00:00 2001 From: angusj Date: Wed, 25 Oct 2023 17:15:48 +1000 Subject: [PATCH 61/95] clipper.export.h minor code tidy --- CPP/Clipper2Lib/include/clipper2/clipper.export.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index 6ebac1d3..3f41fad9 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -218,14 +218,6 @@ static void GetPolytreeCountAndCStorageSize(const PolyTree64& tree, size_t& cnt, array_len = GetPolyPath64ArrayLen(tree); } -static size_t GetPolyPathDArrayLen(const PolyPathD& pp) -{ - size_t result = 4; // magic + is_hole + child_count + poly_length - result += pp.Polygon().size() * 2; - for (size_t i = 0; i < pp.Count(); ++i) - result += GetPolyPathDArrayLen(*pp[i]); -} - static CPaths64 CreateCPaths64(const Paths64& paths) { size_t cnt, array_len; From ee5223f38ecb55f6d8a3265c747e494b30df015c Mon Sep 17 00:00:00 2001 From: angusj Date: Wed, 25 Oct 2023 23:02:04 +1000 Subject: [PATCH 62/95] Updated clipper.export.h again --- .../include/clipper2/clipper.export.h | 49 +++++++++---------- CPP/Tests/TestExportHeaders.cpp | 14 +++--- DLL/Delphi_TestApp/Test_DLL.dpr | 44 ++++++----------- 3 files changed, 45 insertions(+), 62 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index 3f41fad9..8dd17283 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 25 October 2023 * +* Date : 26 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module exports the Clipper2 Library (ie DLL/so) * @@ -31,15 +31,24 @@ // These structures are very similar to their respecitve path structures. These // structures not only contain any number of consecutive CPath64 or CPathD // structures but, preceeding these paths, there is an extra x,y pair of values -// that contains the path count. However, in this case the x value = 0, and -// the y value contains the count (ie the number of following paths). +// that contains the path count. However, in this case the x value = array length, +// and the y value contains the count (ie the number of following paths). // _______________________________ // |counter|path1|path2|...|pathN| -// |0, N | | |...|pathN| +// |len, N | | |...|pathN| // _______________________________ // // -// The CPolytree64 & CPolytreeD structures are defined lower down. +// CPolytree64 and CPolytreeD: +// These are both simple arrays (of int64_t or double respectively). Otherwise +// their structures are identical, and consist of series of CPolyPath structures. +// The CPolyPath structure is as follows: +// Usually polygon length (N) except for the very first CPolyPath entry that +// contains the array size. (The top-most CPolyPath never contains a polygon.) +// ChildCount (C) +// N * x,y coordinates in Polygon +// C * Nested child CPolyPath structures + #ifndef CLIPPER2_EXPORT_H #define CLIPPER2_EXPORT_H @@ -59,9 +68,6 @@ typedef int64_t* CPaths64; typedef double* CPathD; typedef double* CPathsD; -static const int64_t magic_64 = 64; -static const double magic_D = 68; - typedef int64_t* CPolyPath64; typedef int64_t* CPolyTree64; // magic, hole, child_count, poly_len (4 total) + polygon + nested childs @@ -205,8 +211,9 @@ size_t& cnt, size_t& array_len) static size_t GetPolyPath64ArrayLen(const PolyPath64& pp) { - size_t result = 4; // magic + is_hole + child_count + poly_length + size_t result = 2; // poly_length + child_count result += pp.Polygon().size() * 2; + //plus nested children :) for (size_t i = 0; i < pp.Count(); ++i) result += GetPolyPath64ArrayLen(*pp[i]); return result; @@ -223,7 +230,7 @@ static CPaths64 CreateCPaths64(const Paths64& paths) size_t cnt, array_len; GetPathCountAndCPathsArrayLen(paths, cnt, array_len); int64_t* result = new int64_t[array_len], * v = result; - *v++ = 0; + *v++ = array_len; *v++ = cnt; for (const Path64& path : paths) { @@ -244,7 +251,7 @@ static CPathsD CreateCPathsD(const PathsD& paths) size_t cnt, array_len; GetPathCountAndCPathsArrayLen(paths, cnt, array_len); double* result = new double[array_len], * v = result; - *v++ = 0; + *v++ = array_len; *v++ = (double)cnt; for (const PathD& path : paths) { @@ -266,7 +273,7 @@ CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale) size_t cnt, array_len; GetPathCountAndCPathsArrayLen(paths, cnt, array_len); CPathsD result = new double[array_len], v = result; - *v++ = 0; + *v++ = array_len; *v++ = (double)cnt; for (const Path64& path : paths) { @@ -355,10 +362,8 @@ static Paths64 ConvertCPathsDToPaths64(const CPathsD paths, double scale) static void CreateCPolyPath64(const PolyPath64* pp, CPolyPath64& v) { - *v++ = magic_64; - *v++ = pp->IsHole() ? 1 : 0; - *v++ = pp->Count(); *v++ = pp->Polygon().size(); + *v++ = pp->Count(); for (const Point64& pt : pp->Polygon()) { *v++ = pt.x; @@ -377,10 +382,8 @@ static CPolyTree64 CreateCPolyTree64(const PolyTree64& tree) int64_t* result = new int64_t[array_len]; int64_t* v = &result[0]; - *v++ = magic_64; - *v++ = 0; + *v++ = array_len; *v++ = tree.Count(); - *v++ = 0; for (size_t i = 0; i < tree.Count(); ++i) CreateCPolyPath64(tree.Child(i), v); return result; @@ -388,10 +391,8 @@ static CPolyTree64 CreateCPolyTree64(const PolyTree64& tree) static void CreateCPolyPathD(const PolyPath64* pp, CPolyPathD& v, double scale) { - *v++ = magic_64; - *v++ = pp->IsHole() ? 1 : 0; - *v++ = (double)pp->Count(); *v++ = (double)pp->Polygon().size(); + *v++ = (double)pp->Count(); for (const Point64& pt : pp->Polygon()) { *v++ = pt.x * scale; @@ -409,12 +410,10 @@ static CPolyTreeD CreateCPolyTreeD(const PolyTree64& tree, double scale) if (!cnt) return nullptr; // allocate storage double* result = new double[array_len]; - double* v = &result[0]; + double* v = result; - *v++ = magic_64; - *v++ = 0; + *v++ = array_len; *v++ = (double)tree.Count(); - *v++ = 0; for (size_t i = 0; i < tree.Count(); ++i) CreateCPolyPathD(tree.Child(i), v, scale); return result; diff --git a/CPP/Tests/TestExportHeaders.cpp b/CPP/Tests/TestExportHeaders.cpp index af761573..01557700 100644 --- a/CPP/Tests/TestExportHeaders.cpp +++ b/CPP/Tests/TestExportHeaders.cpp @@ -8,8 +8,8 @@ using namespace Clipper2Lib; static bool CreatePolyPath64FromCPolyPath(CPolyPath64& v, PolyPath64& owner) { - int64_t magic = *v++, hole = *v++, child_count = *v++, poly_len = *v++; - if (magic != magic_64 || !poly_len) return false; + int64_t poly_len = *v++, child_count = *v++; + if (!poly_len) return false; Path64 path; path.reserve(poly_len); for (size_t i = 0; i < poly_len; ++i) @@ -28,8 +28,7 @@ static bool BuildPolyTree64FromCPolyTree(CPolyTree64 tree, PolyTree64& result) { result.Clear(); int64_t* v = tree; - int64_t magic = *v++, hole = *v++, child_count = *v++, poly_len = *v++; - if (magic != magic_64 || poly_len) return false; + int64_t array_len = *v++, child_count = *v++; for (size_t i = 0; i < child_count; ++i) if (!CreatePolyPath64FromCPolyPath(v, result)) return false; return true; @@ -37,8 +36,8 @@ static bool BuildPolyTree64FromCPolyTree(CPolyTree64 tree, PolyTree64& result) static bool CreatePolyPathDFromCPolyPath(CPolyPathD& v, PolyPathD& owner) { - int64_t magic = *v++, hole = *v++, child_count = *v++, poly_len = *v++; - if (magic != magic_64 || !poly_len) return false; + int64_t poly_len = *v++, child_count = *v++; + if (!poly_len) return false; PathD path; path.reserve(poly_len); for (size_t i = 0; i < poly_len; ++i) @@ -56,8 +55,7 @@ static bool BuildPolyTreeDFromCPolyTree(CPolyTreeD tree, PolyTreeD& result) { result.Clear(); double* v = tree; - int64_t magic = *v++, hole = *v++, child_count = *v++, poly_len = *v++; - if (magic != magic_64 || poly_len) return false; + int64_t array_len = *v++, child_count = *v++; for (size_t i = 0; i < child_count; ++i) if (!CreatePolyPathDFromCPolyPath(v, result)) return false; return true; diff --git a/DLL/Delphi_TestApp/Test_DLL.dpr b/DLL/Delphi_TestApp/Test_DLL.dpr index 39e56683..775dbc36 100644 --- a/DLL/Delphi_TestApp/Test_DLL.dpr +++ b/DLL/Delphi_TestApp/Test_DLL.dpr @@ -111,7 +111,6 @@ function RectClipLinesD(const rect: TRectD; const Intersection = 1; Union = 2; Difference =3; Xor_ = 4; EvenOdd = 0; NonZero = 1; Positive = 2; Negative = 3; - magic_64 = 64; magic_D = 68; //////////////////////////////////////////////////////// // functions related to Clipper2 DLL structures @@ -142,7 +141,7 @@ begin if Length(pp[i]) > 0 then inc(len2, Length(pp[i]) *2 + 2); GetMem(Result, len2 * sizeof(Int64)); - Result[0] := 0; + Result[0] := len2; Result[1] := len; v := @Result[2]; for i := 0 to len -1 do @@ -170,7 +169,7 @@ begin if Length(pp[i]) > 0 then inc(len2, Length(pp[i]) *2 + 2); GetMem(Result, len2 * sizeof(double)); - Result[0] := 0; + Result[0] := len2; Result[1] := len; v := @Result[2]; for i := 0 to len -1 do @@ -194,7 +193,7 @@ var begin Result := nil; v := PInt64(cp); - if v^ <> 0 then Exit; inc(v); + inc(v); // ignore array length len := v^; inc(v); SetLength(Result, len); for i := 0 to len -1 do @@ -215,10 +214,10 @@ var v: PDouble; begin Result := nil; - if cp[0] <> 0 then Exit; - len := Round(cp[1]); + v := PDouble(cp); + inc(v); // ignore array length + len := Round(cp[1]); inc(v); SetLength(Result, len); - v := @cp[2]; for i := 0 to len -1 do begin len2 := Round(v^); inc(v, 2); @@ -235,7 +234,7 @@ function GetPolyPath64ArrayLen(const pp: TPolyPath64): integer; var i: integer; begin - Result := 4; // magic + is_hole + child_count + poly_length + 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])); @@ -253,14 +252,9 @@ procedure CreateCPolyPathD(const pp: TPolyPath64; var i, len: integer; begin - v^ := magic_64; inc(v); - if pp.IsHole then - v^ := 1 else - v^ := 0; - inc(v); - v^ := pp.Count; inc(v); 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; @@ -283,10 +277,8 @@ begin GetMem(Result, arrayLen * SizeOf(double)); v := PDouble(Result); - v^ := magic_64; inc(v); - v^ := 0; inc(v); + v^ := arrayLen; inc(v); v^ := tree.Count; inc(v); - v^ := 0; inc(v); for i := 0 to tree.Count - 1 do CreateCPolyPathD(tree.Child[i], v, scale); end; @@ -298,10 +290,9 @@ var newOwner: TPolyPath64; begin Result := false; - magic := v^; inc(v, 2); + len := v^; inc(v); //polygon length childCount := v^; inc(v); - len := v^; inc(v); - if (magic <> magic_64) or (len = 0) then Exit; + if (len = 0) then Exit; SetLength(path, len); for i := 0 to len -1 do begin @@ -322,10 +313,8 @@ begin Result := false; outTree.Clear(); v := PInt64(tree); - magic := v^; inc(v, 2); + inc(v); //skip array size childCount := v^; inc(v); - len := v^; inc(v); - if (magic <> magic_64) or (len > 0) then Exit; for i := 0 to childCount -1 do if not CreatePolyPath64FromCPolyPath(v, outTree) then Exit; Result := true; @@ -338,10 +327,9 @@ var newOwner: TPolyPathD; begin Result := false; - magic := Round(v^); inc(v, 2); - childCount := Round(v^); inc(v); len := Round(v^); inc(v); - if (magic <> magic_64) or (len = 0) then Exit; + childCount := Round(v^); inc(v); + if (len = 0) then Exit; SetLength(path, len); for i := 0 to len -1 do begin @@ -362,10 +350,8 @@ begin Result := false; outTree.Clear(); v := PDouble(tree); - magic := Round(v^); inc(v, 2); + inc(v); // ignore array size childCount := Round(v^); inc(v); - len := Round(v^); inc(v); - if (magic <> magic_64) or (len > 0) then Exit; for i := 0 to childCount -1 do if not CreatePolyPathDFromCPolyPath(v, outTree) then Exit; Result := true; From d56becdac037e7f85abc88ac7b9c966fad501ac8 Mon Sep 17 00:00:00 2001 From: angusj Date: Thu, 26 Oct 2023 15:23:28 +1000 Subject: [PATCH 63/95] Added a simple C# demo that uses a Clipper2 DLL Can now alter MAX_DECIMAL_PRECISION via CMakeLists.txt (Disc.564) --- CPP/CMakeLists.txt | 11 +- .../include/clipper2/clipper.core.h | 9 +- .../include/clipper2/clipper.export.h | 3 - DLL/CSharp_TestApp/ADD Clipper2_64.dll HERE | 0 DLL/CSharp_TestApp/CSharp_TestApp.csproj | 10 + DLL/CSharp_TestApp/CSharp_TestApp.sln | 25 +++ DLL/CSharp_TestApp/Program.cs | 188 ++++++++++++++++++ DLL/Delphi_TestApp/ADD Clipper2_64.dll HERE | 0 8 files changed, 240 insertions(+), 6 deletions(-) create mode 100644 DLL/CSharp_TestApp/ADD Clipper2_64.dll HERE create mode 100644 DLL/CSharp_TestApp/CSharp_TestApp.csproj create mode 100644 DLL/CSharp_TestApp/CSharp_TestApp.sln create mode 100644 DLL/CSharp_TestApp/Program.cs create mode 100644 DLL/Delphi_TestApp/ADD Clipper2_64.dll HERE diff --git a/CPP/CMakeLists.txt b/CPP/CMakeLists.txt index b7106d4a..0230ee10 100644 --- a/CPP/CMakeLists.txt +++ b/CPP/CMakeLists.txt @@ -16,6 +16,7 @@ option(USE_EXTERNAL_GTEST "Use system-wide installed GoogleTest" OFF) option(USE_EXTERNAL_GBENCHMARK "Use the googlebenchmark" OFF) option(BUILD_SHARED_LIBS "Build shared libs" OFF) set(CLIPPER2_USINGZ "ON" CACHE STRING "Build Clipper2Z, either \"ON\" or \"OFF\" or \"ONLY\"") +set(CLIPPER2_MAX_PRECISION 8 CACHE STRING "Maximum precision allowed for double to int64 scaling") if (APPLE) set(CMAKE_SHARED_LIBRARY_SUFFIX ".so") @@ -50,6 +51,11 @@ if (NOT (CLIPPER2_USINGZ STREQUAL "ONLY")) list(APPEND CLIPPER2_LIBS Clipper2) add_library(Clipper2 ${CLIPPER2_INC} ${CLIPPER2_SRC}) + target_compile_definitions( + Clipper2 + PUBLIC CLIPPER2_MAX_PRECISION=${CLIPPER2_MAX_PRECISION} + ) + target_include_directories(Clipper2 PUBLIC Clipper2Lib/include ) @@ -67,7 +73,10 @@ if (NOT (CLIPPER2_USINGZ STREQUAL "OFF")) list(APPEND CLIPPER2_LIBS Clipper2Z) add_library(Clipper2Z ${CLIPPER2_INC} ${CLIPPER2_SRC}) - target_compile_definitions(Clipper2Z PUBLIC USINGZ) + target_compile_definitions( + Clipper2Z + PUBLIC USINGZ CLIPPER2_MAX_PRECISION=${CLIPPER2_MAX_PRECISION} + ) target_include_directories(Clipper2Z PUBLIC Clipper2Lib/include diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index d1849cad..fe67a279 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -19,7 +19,6 @@ #include #include #include - #include "clipper2/clipper.version.h" namespace Clipper2Lib @@ -55,7 +54,13 @@ namespace Clipper2Lib #ifndef PI static const double PI = 3.141592653589793238; #endif - static const int MAX_DECIMAL_PRECISION = 8; // see https://github.com/AngusJohnson/Clipper2/discussions/564 + +#ifdef CLIPPER2_MAX_PRECISION + const int MAX_DECIMAL_PRECISION = CLIPPER2_MAX_PRECISION; +#else + const int MAX_DECIMAL_PRECISION = 8; // see Discussions #564 +#endif + static const int64_t MAX_COORD = INT64_MAX >> 2; static const int64_t MIN_COORD = -MAX_COORD; static const int64_t INVALID = INT64_MAX; diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index 8dd17283..60a9beb0 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -70,11 +70,8 @@ typedef double* CPathsD; typedef int64_t* CPolyPath64; typedef int64_t* CPolyTree64; -// magic, hole, child_count, poly_len (4 total) + polygon + nested childs - typedef double* CPolyPathD; typedef double* CPolyTreeD; -// magic, hole, child_count, poly_len (4 total) + polygon + nested childs template struct CRect { diff --git a/DLL/CSharp_TestApp/ADD Clipper2_64.dll HERE b/DLL/CSharp_TestApp/ADD Clipper2_64.dll HERE new file mode 100644 index 00000000..e69de29b diff --git a/DLL/CSharp_TestApp/CSharp_TestApp.csproj b/DLL/CSharp_TestApp/CSharp_TestApp.csproj new file mode 100644 index 00000000..74abf5c9 --- /dev/null +++ b/DLL/CSharp_TestApp/CSharp_TestApp.csproj @@ -0,0 +1,10 @@ + + + + Exe + net6.0 + enable + enable + + + diff --git a/DLL/CSharp_TestApp/CSharp_TestApp.sln b/DLL/CSharp_TestApp/CSharp_TestApp.sln new file mode 100644 index 00000000..cfec493a --- /dev/null +++ b/DLL/CSharp_TestApp/CSharp_TestApp.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33801.468 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharp_TestApp", "CSharp_TestApp.csproj", "{CFC62F44-8150-4BD9-A65F-2FC3876F561C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CFC62F44-8150-4BD9-A65F-2FC3876F561C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CFC62F44-8150-4BD9-A65F-2FC3876F561C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFC62F44-8150-4BD9-A65F-2FC3876F561C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CFC62F44-8150-4BD9-A65F-2FC3876F561C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F0327373-C3AF-400F-B7EB-33118D1EEE68} + EndGlobalSection +EndGlobal diff --git a/DLL/CSharp_TestApp/Program.cs b/DLL/CSharp_TestApp/Program.cs new file mode 100644 index 00000000..b2c8bb3c --- /dev/null +++ b/DLL/CSharp_TestApp/Program.cs @@ -0,0 +1,188 @@ +/******************************************************************************* +* 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.Runtime.InteropServices; + +namespace ClipperDllDemo +{ + public class Application + { + + // Define essential Clipper2 structures /////////////////////// + public struct Point64 + { + public long X; + public long Y; + public Point64(long x, long y) + { + X = x; + Y = y; + } + public readonly override string ToString() + { + return $"{X},{Y} "; + } + } + + public struct Rect64 + { + public long left; + public long top; + public long right; + public long bottom; + public Rect64(long l, long t, long r, long b) + { + left = l; + top = t; + right = r; + bottom = b; + } + } + + public class Path64 : List + { + private Path64() : base() { } + public Path64(int capacity = 0) : base(capacity) { } + public Path64(IEnumerable path) : base(path) { } + public override string ToString() + { + string s = ""; + foreach (Point64 p in this) + s = s + p.ToString() + " "; + return s; + } + } + + public class Paths64 : List + { + private Paths64() : base() { } + public Paths64(int capacity = 0) : base(capacity) { } + public Paths64(IEnumerable paths) : base(paths) { } + public override string ToString() + { + string s = ""; + foreach (Path64 p in this) + s = s + p.ToString() + "\n"; + return s; + } + } + + // Define miscellaneous functions //////////////////////////// + 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; + } + + 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 = "DisposeExportedCPaths64", CallingConvention = CallingConvention.Cdecl)] + static extern void DisposeExportedCPaths64(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"); + + Paths64 subject = new() {MakePath(new int[] { 0, 0, 100, 0, 100, 100, 0, 100 })}; + Paths64 clip = new() {MakePath(new int[] { 20, 20, 120, 20, 120, 120, 20, 120 })}; + + long[] cSubject = CreateCPaths64(subject); + long[] cClip = CreateCPaths64(clip); + + int result = BooleanOp64(Intersection, EvenOdd, cSubject, + null, cClip, out IntPtr cSol, out IntPtr cSolOpen, false, false); + + if (result != 0) return; + + long[]? cSolution = GetPathsFromIntPtr(cSol); + DisposeExportedCPaths64(ref cSol); + DisposeExportedCPaths64(ref cSolOpen); + if (cSolution == null) return; + + Paths64 solution = GetPaths64FromCPaths(cSolution); + Console.WriteLine(solution.ToString()); + Console.ReadLine(); + } + + } //end Application +} //namespace diff --git a/DLL/Delphi_TestApp/ADD Clipper2_64.dll HERE b/DLL/Delphi_TestApp/ADD Clipper2_64.dll HERE new file mode 100644 index 00000000..e69de29b From 615a18dd64ccab12252d83dc3b1161051257d0c3 Mon Sep 17 00:00:00 2001 From: angusj Date: Fri, 27 Oct 2023 21:19:29 +1000 Subject: [PATCH 64/95] Updated Clipper2.DLL test applications Fixed a bug when offsetting a single point in C# (Disc.699) --- .../include/clipper2/clipper.export.h | 7 +- CSharp/Clipper2Lib/Clipper.Offset.cs | 7 +- ...E => COMPILE AND ADD Clipper2_64.dll HERE} | 0 DLL/CSharp_TestApp/CSharp_TestApp.csproj | 1 + DLL/CSharp_TestApp/CSharp_TestApp.sln | 14 +- DLL/CSharp_TestApp/Program.cs | 187 +++++++++++------- .../COMPILE AND ADD Clipper2_64.dll HERE} | 0 DLL/CSharp_TestApp2/CSharp_TestApp2.csproj | 16 ++ DLL/CSharp_TestApp2/CSharp_TestApp2.sln | 63 ++++++ DLL/CSharp_TestApp2/Program.cs | 186 +++++++++++++++++ .../COMPILE AND ADD Clipper2_64.dll HERE | 0 11 files changed, 397 insertions(+), 84 deletions(-) rename DLL/CSharp_TestApp/{ADD Clipper2_64.dll HERE => COMPILE AND ADD Clipper2_64.dll HERE} (100%) rename DLL/{Delphi_TestApp/ADD Clipper2_64.dll HERE => CSharp_TestApp2/COMPILE AND ADD Clipper2_64.dll HERE} (100%) create mode 100644 DLL/CSharp_TestApp2/CSharp_TestApp2.csproj create mode 100644 DLL/CSharp_TestApp2/CSharp_TestApp2.sln create mode 100644 DLL/CSharp_TestApp2/Program.cs create mode 100644 DLL/Delphi_TestApp/COMPILE AND ADD Clipper2_64.dll HERE diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index 60a9beb0..bd51365d 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -248,7 +248,7 @@ static CPathsD CreateCPathsD(const PathsD& paths) size_t cnt, array_len; GetPathCountAndCPathsArrayLen(paths, cnt, array_len); double* result = new double[array_len], * v = result; - *v++ = array_len; + *v++ = (double)array_len; *v++ = (double)cnt; for (const PathD& path : paths) { @@ -270,7 +270,7 @@ CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale) size_t cnt, array_len; GetPathCountAndCPathsArrayLen(paths, cnt, array_len); CPathsD result = new double[array_len], v = result; - *v++ = array_len; + *v++ = (double)array_len; *v++ = (double)cnt; for (const Path64& path : paths) { @@ -409,7 +409,7 @@ static CPolyTreeD CreateCPolyTreeD(const PolyTree64& tree, double scale) double* result = new double[array_len]; double* v = result; - *v++ = array_len; + *v++ = (double)array_len; *v++ = (double)tree.Count(); for (size_t i = 0; i < tree.Count(); ++i) CreateCPolyPathD(tree.Child(i), v, scale); @@ -506,7 +506,6 @@ EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, 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); return 0; diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index eb9eba0b..a1bd64c7 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 24 September 2023 * +* Date : 26 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -53,7 +53,8 @@ public Group(Paths64 paths, JoinType joinType, EndType endType = EndType.Polygon } } - private static double Tolerance = 1.0E-12; + private static readonly double Tolerance = 1.0E-12; + private readonly List _groupList = new List(); private readonly PathD _normals = new PathD(); private readonly Paths64 _solution = new Paths64(); @@ -674,7 +675,7 @@ private void DoGroupOffset(Group group) { int d = (int) Math.Ceiling(_groupDelta); Rect64 r = new Rect64(path[0].X - d, path[0].Y - d, - path[0].X - d, path[0].Y - d); + path[0].X + d, path[0].Y + d); group.outPath = r.AsPath(); #if USINGZ group.outPath = InternalClipper.SetZ(group.outPath, path[0].Z); diff --git a/DLL/CSharp_TestApp/ADD Clipper2_64.dll HERE b/DLL/CSharp_TestApp/COMPILE AND ADD Clipper2_64.dll HERE similarity index 100% rename from DLL/CSharp_TestApp/ADD Clipper2_64.dll HERE rename to DLL/CSharp_TestApp/COMPILE AND ADD Clipper2_64.dll HERE diff --git a/DLL/CSharp_TestApp/CSharp_TestApp.csproj b/DLL/CSharp_TestApp/CSharp_TestApp.csproj index 74abf5c9..fe6a125f 100644 --- a/DLL/CSharp_TestApp/CSharp_TestApp.csproj +++ b/DLL/CSharp_TestApp/CSharp_TestApp.csproj @@ -5,6 +5,7 @@ net6.0 enable enable + x64 diff --git a/DLL/CSharp_TestApp/CSharp_TestApp.sln b/DLL/CSharp_TestApp/CSharp_TestApp.sln index cfec493a..961e12cb 100644 --- a/DLL/CSharp_TestApp/CSharp_TestApp.sln +++ b/DLL/CSharp_TestApp/CSharp_TestApp.sln @@ -3,18 +3,18 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.6.33801.468 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharp_TestApp", "CSharp_TestApp.csproj", "{CFC62F44-8150-4BD9-A65F-2FC3876F561C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharp_TestApp", "CSharp_TestApp.csproj", "{CFC62F44-8150-4BD9-A65F-2FC3876F561C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CFC62F44-8150-4BD9-A65F-2FC3876F561C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CFC62F44-8150-4BD9-A65F-2FC3876F561C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CFC62F44-8150-4BD9-A65F-2FC3876F561C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CFC62F44-8150-4BD9-A65F-2FC3876F561C}.Release|Any CPU.Build.0 = Release|Any CPU + {CFC62F44-8150-4BD9-A65F-2FC3876F561C}.Debug|x64.ActiveCfg = Debug|x64 + {CFC62F44-8150-4BD9-A65F-2FC3876F561C}.Debug|x64.Build.0 = Debug|x64 + {CFC62F44-8150-4BD9-A65F-2FC3876F561C}.Release|x64.ActiveCfg = Release|x64 + {CFC62F44-8150-4BD9-A65F-2FC3876F561C}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/DLL/CSharp_TestApp/Program.cs b/DLL/CSharp_TestApp/Program.cs index b2c8bb3c..c5a125f3 100644 --- a/DLL/CSharp_TestApp/Program.cs +++ b/DLL/CSharp_TestApp/Program.cs @@ -1,11 +1,12 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 October 2023 * +* Date : 27 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 @@ -13,113 +14,118 @@ namespace ClipperDllDemo public class Application { - // Define essential Clipper2 structures /////////////////////// - public struct Point64 + // Define path structures somewhat similar to those in Clipper2 + + // nb: We don't need to use path structures that are similar Clipper2's + // structures, but it does demonstrate how to parse the CPaths64 and CPathsD + // structures in cipper.export.h. + + public class Point { - public long X; - public long Y; - public Point64(long x, long y) + public T X; + public T Y; + private readonly int precision = 2; + public Point(T x, T y) { X = x; Y = y; } - public readonly override string ToString() + public override string ToString() { - return $"{X},{Y} "; + if (typeof(T) == typeof(long)) + return $"{X},{Y} "; + else + return string.Format($"{{0:F{precision}}},{{1:F{precision}}} ", X, Y); } } - public struct Rect64 + public struct Rect { - public long left; - public long top; - public long right; - public long bottom; - public Rect64(long l, long t, long r, long b) + 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; + left = l; top = t; right = r; bottom = b; } } - - public class Path64 : List + + public class Path : List> { - private Path64() : base() { } - public Path64(int capacity = 0) : base(capacity) { } - public Path64(IEnumerable path) : base(path) { } + private Path() : base() { } + public Path(int capacity = 0) : base(capacity) { } + public Path(IEnumerable> path) : base(path) { } public override string ToString() { string s = ""; - foreach (Point64 p in this) + foreach (Point p in this) s = s + p.ToString() + " "; return s; } } - public class Paths64 : List + public class Paths : List> { - private Paths64() : base() { } - public Paths64(int capacity = 0) : base(capacity) { } - public Paths64(IEnumerable paths) : base(paths) { } + private Paths() : base() { } + public Paths(int capacity = 0) : base(capacity) { } + public Paths(IEnumerable> paths) : base(paths) { } public override string ToString() { string s = ""; - foreach (Path64 p in this) + foreach (Path p in this) s = s + p.ToString() + "\n"; return s; } } - // Define miscellaneous functions //////////////////////////// - public static Path64 MakePath(int[] arr) + // Miscellaneous functions //////////////////////////// + public static Path MakePath(T[] arr) { int len = arr.Length / 2; - Path64 p = new(len); + Path p = new(len); for (int i = 0; i < len; i++) - p.Add(new Point64(arr[i * 2], arr[i * 2 + 1])); + p.Add(new Point(arr[i * 2], arr[i * 2 + 1])); return p; } - - static Path64 GetPath64FromCPath(long[] cpaths, ref int idx) + static Path GetPathFromCPath(T[] cpath, ref int idx) { - int cnt = (int)cpaths[idx]; idx += 2; - Path64 result = new(cnt); + int cnt = Convert.ToInt32(cpath[idx]); + idx += 2; + Path result = new(cnt); for (int i = 0; i < cnt; i++) { - long x = cpaths[idx++]; - long y = cpaths[idx++]; - result.Add(new Point64(x, y)); + T x = cpath[idx++]; + T y = cpath[idx++]; + result.Add(new Point(x, y)); } return result; } - - static Paths64 GetPaths64FromCPaths(long[] cpaths) + static Paths GetPathsFromCPaths(T[] cpaths) { - int cnt = (int)cpaths[1], idx = 2; - Paths64 result = new(cnt); + int cnt = Convert.ToInt32(cpaths[1]); + int idx = 2; + Paths result = new(cnt); for (int i = 0; i < cnt; i++) - result.Add(GetPath64FromCPath(cpaths, ref idx)); + result.Add(GetPathFromCPath(cpaths, ref idx)); return result; } - - static long[] CreateCPaths64(Paths64 pp) + static T[] CreateCPaths(Paths 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; + T[] result = new T[len2]; + result[0] = (T)Convert.ChangeType(0, typeof(T)); + result[1] = (T)Convert.ChangeType(len, typeof(T)); int rPos = 2; for (int i = 0; i < len; i++) { len2 = pp[i].Count; if (len2 == 0) continue; - result[rPos++] = len2; - result[rPos++] = 0; + result[rPos++] = (T)Convert.ChangeType(len2, typeof(T)); + result[rPos++] = (T)Convert.ChangeType(0, typeof(T)); for (int j = 0; j < len2; j++) { result[rPos++] = pp[i][j].X; @@ -129,58 +135,99 @@ static long[] CreateCPaths64(Paths64 pp) return result; } - public static long[]? GetPathsFromIntPtr(IntPtr paths) + public static long[]? GetPathsFromIntPtr64(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]]; + long[] result = new long[(int)len[0]]; + Marshal.Copy(paths, result, 0, (int)len[0]); + return result; + } + + public static double[]? GetPathsFromIntPtrD(IntPtr paths) + { + if (paths == IntPtr.Zero) return null; + double[] len = new double[1]; + Marshal.Copy(paths, len, 0, 1); + double[] result = new double[(int)len[0]]; Marshal.Copy(paths, result, 0, (int)len[0]); return result; } - // Define DLL exported functions ///////////////////// + // DLL exported function definitions ///////////////////// - public const string clipperDll = @"..\..\..\Clipper2_64.dll"; + 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, + static extern Int32 BooleanOp64(byte clipType, byte fillRule, long[] subject, long[]? subOpen, long[]? clip, out IntPtr solution, out IntPtr solOpen, bool preserveCollinear, bool reverseSolution); + [DllImport(clipperDll, EntryPoint = "BooleanOpD", CallingConvention = CallingConvention.Cdecl)] + static extern Int32 BooleanOpD(byte clipType, byte fillRule, + double[] subject, double[]? subOpen, double[]? clip, + out IntPtr solution, out IntPtr solOpen, Int32 precision, bool preserveCollinear, bool reverseSolution); + [DllImport(clipperDll, EntryPoint = "DisposeExportedCPaths64", CallingConvention = CallingConvention.Cdecl)] static extern void DisposeExportedCPaths64(ref IntPtr paths); - + [DllImport(clipperDll, EntryPoint = "DisposeExportedCPathsD", CallingConvention = CallingConvention.Cdecl)] + static extern void DisposeExportedCPathsD(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; + + /// Main Entry ///////////////////////////////////////////////////////////////////// public static void Main() { - string? ver = Marshal.PtrToStringAnsi(Version()); - Console.WriteLine(ver +"\n"); + //string? ver = Marshal.PtrToStringAnsi(Version()); + //Console.WriteLine(ver +"\n"); - Paths64 subject = new() {MakePath(new int[] { 0, 0, 100, 0, 100, 100, 0, 100 })}; - Paths64 clip = new() {MakePath(new int[] { 20, 20, 120, 20, 120, 120, 20, 120 })}; + // test BooleanOp64() //////////////////////////////////////////////////////////// + Paths subject = new() {MakePath(new long[] { 0, 0, 100, 0, 100, 100, 0, 100 })}; + Paths clip = new() {MakePath(new long[] { 20, 20, 120, 20, 120, 120, 20, 120 })}; + long[] cSubject = CreateCPaths(subject); + long[] cClip = CreateCPaths(clip); - long[] cSubject = CreateCPaths64(subject); - long[] cClip = CreateCPaths64(clip); - - int result = BooleanOp64(Intersection, EvenOdd, cSubject, + int result = BooleanOp64(Intersection, NonZero, cSubject, null, cClip, out IntPtr cSol, out IntPtr cSolOpen, false, false); - if (result != 0) return; - long[]? cSolution = GetPathsFromIntPtr(cSol); + long[]? cSolution = GetPathsFromIntPtr64(cSol); DisposeExportedCPaths64(ref cSol); DisposeExportedCPaths64(ref cSolOpen); if (cSolution == null) return; - - Paths64 solution = GetPaths64FromCPaths(cSolution); + Paths solution = GetPathsFromCPaths(cSolution); + Console.WriteLine(solution.ToString()); + ////////////////////////////////////////////////////////////////////////////////// + + // test BooleanOpD() ///////////////////////////////////////////////////////////// + Paths subjectD = new() { MakePath(new double[] { 0, 0, 100, 0, 100, 100, 0, 100 }) }; + Paths clipD = new() { MakePath(new double[] { 20, 20, 120, 20, 120, 120, 20, 120 }) }; + double[] cSubjectD = CreateCPaths(subjectD); + double[] cClipD = CreateCPaths(clipD); + + int resultD = BooleanOpD(Intersection, NonZero, cSubjectD, + null, cClipD, out IntPtr cSolD, out IntPtr cSolOpenD, 2, false, false); + if (resultD != 0) return; + + double[]? cSolutionD = GetPathsFromIntPtrD(cSolD); + DisposeExportedCPathsD(ref cSolD); + DisposeExportedCPathsD(ref cSolOpenD); + if (cSolutionD == null) return; + Paths solutionD = GetPathsFromCPaths(cSolutionD); + + Console.WriteLine(solutionD.ToString()); + ////////////////////////////////////////////////////////////////////////////////// + + + Console.WriteLine("Press ENTER to exit ... "); Console.ReadLine(); } diff --git a/DLL/Delphi_TestApp/ADD Clipper2_64.dll HERE b/DLL/CSharp_TestApp2/COMPILE AND ADD Clipper2_64.dll HERE similarity index 100% rename from DLL/Delphi_TestApp/ADD Clipper2_64.dll HERE rename to DLL/CSharp_TestApp2/COMPILE AND ADD Clipper2_64.dll HERE diff --git a/DLL/CSharp_TestApp2/CSharp_TestApp2.csproj b/DLL/CSharp_TestApp2/CSharp_TestApp2.csproj new file mode 100644 index 00000000..85e03793 --- /dev/null +++ b/DLL/CSharp_TestApp2/CSharp_TestApp2.csproj @@ -0,0 +1,16 @@ + + + + Exe + net6.0 + enable + enable + x64 + + + + + + + + diff --git a/DLL/CSharp_TestApp2/CSharp_TestApp2.sln b/DLL/CSharp_TestApp2/CSharp_TestApp2.sln new file mode 100644 index 00000000..3b598060 --- /dev/null +++ b/DLL/CSharp_TestApp2/CSharp_TestApp2.sln @@ -0,0 +1,63 @@ + +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 new file mode 100644 index 00000000..85116bd2 --- /dev/null +++ b/DLL/CSharp_TestApp2/Program.cs @@ -0,0 +1,186 @@ +/******************************************************************************* +* 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 = "DisposeExportedCPaths64", CallingConvention = CallingConvention.Cdecl)] + static extern void DisposeExportedCPaths64(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 = 2000; + //////////////////////////////////////////////////////////////////////// + + Paths64 subject = new() { MakeRandomPath(600,400, edgeCount, rand)}; + Paths64 clip = new() { MakeRandomPath(600, 400, edgeCount, rand) }; + + ////////////////////////////////////////////////////////////////////// + // Use Clipper2's C++ compiled library (ie use the DLL) + // NB: this time includes the overhead of swapping path structures + Stopwatch sw1 = Stopwatch.StartNew(); + long[] cSubject = CreateCPaths64(subject); + long[] cClip = CreateCPaths64(clip); + int result = BooleanOp64(Intersection, NonZero, cSubject, + null, cClip, out IntPtr cSol, out IntPtr cSolOpen, false, false); + sw1.Stop(); + if (result != 0) return; + timeMsec = sw1.ElapsedMilliseconds; + Console.WriteLine($"Time using DLL (C++ code): {timeMsec} ms"); + ////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + // Use Clipper2's 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"); + ////////////////////////////////////////////////////////////////////// + + long[]? cSolution = GetPathsFromIntPtr(cSol); + DisposeExportedCPaths64(ref cSol); + DisposeExportedCPaths64(ref cSolOpen); + if (cSolution == null) return; + + Paths64 solution = GetPaths64FromCPaths(cSolution); + //Console.WriteLine(solution.ToString()); + + 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 ENTER to exit ... "); + //Console.ReadLine(); + } + + } //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 new file mode 100644 index 00000000..e69de29b From 0b0dd11a9150d3577c90b601f671275c6c64d433 Mon Sep 17 00:00:00 2001 From: angusj Date: Sat, 28 Oct 2023 11:46:59 +1000 Subject: [PATCH 65/95] Updated DLL test app --- DLL/CSharp_TestApp/Program.cs | 159 ++++++++++++++++++++++++++-------- 1 file changed, 121 insertions(+), 38 deletions(-) diff --git a/DLL/CSharp_TestApp/Program.cs b/DLL/CSharp_TestApp/Program.cs index c5a125f3..811c3da0 100644 --- a/DLL/CSharp_TestApp/Program.cs +++ b/DLL/CSharp_TestApp/Program.cs @@ -8,6 +8,7 @@ using System; using System.Runtime.InteropServices; +using static ClipperDllDemo.Application; namespace ClipperDllDemo { @@ -24,16 +25,15 @@ public class Point { public T X; public T Y; - private readonly int precision = 2; public Point(T x, T y) { X = x; Y = y; } - public override string ToString() + public string ToString(int precision = 0) { - if (typeof(T) == typeof(long)) - return $"{X},{Y} "; + if (typeof(T) == typeof(long)) // ignore precision + return $"{X},{Y} "; else return string.Format($"{{0:F{precision}}},{{1:F{precision}}} ", X, Y); } @@ -50,17 +50,17 @@ public Rect(T l, T t, T r, T b) left = l; top = t; right = r; bottom = b; } } - + public class Path : List> { private Path() : base() { } public Path(int capacity = 0) : base(capacity) { } public Path(IEnumerable> path) : base(path) { } - public override string ToString() + public string ToString(int precision = 0) { string s = ""; foreach (Point p in this) - s = s + p.ToString() + " "; + s = s + p.ToString(precision) + " "; return s; } } @@ -70,11 +70,11 @@ public class Paths : List> private Paths() : base() { } public Paths(int capacity = 0) : base(capacity) { } public Paths(IEnumerable> paths) : base(paths) { } - public override string ToString() + public string ToString(int precision = 0) { string s = ""; foreach (Path p in this) - s = s + p.ToString() + "\n"; + s = s + p.ToString(precision) + "\n"; return s; } } @@ -135,26 +135,54 @@ static T[] CreateCPaths(Paths pp) return result; } - public static long[]? GetPathsFromIntPtr64(IntPtr paths) + public static void DisplayPolyPath(T[] polypath, ref int idx, bool isHole, string spaceIndent) { - if (paths == IntPtr.Zero) return null; - long[] len = new long[1]; - Marshal.Copy(paths, len, 0, 1); - long[] result = new long[(int)len[0]]; - Marshal.Copy(paths, result, 0, (int)len[0]); - return result; + 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++) + { + Point pt = new Point(polypath[idx++], polypath[idx++]); + Console.Write(pt.ToString(2)); + } + Console.Write("\n"); + for (int i = 0; i < childCnt; i++) + DisplayPolyPath(polypath, ref idx, !isHole, spaceIndent); } - public static double[]? GetPathsFromIntPtrD(IntPtr paths) + public static void DisplayPolytree(T[] polytree) { - if (paths == IntPtr.Zero) return null; - double[] len = new double[1]; - Marshal.Copy(paths, len, 0, 1); - double[] result = new double[(int)len[0]]; - Marshal.Copy(paths, result, 0, (int)len[0]); - return result; + int cnt = Convert.ToInt32(polytree[1]); + int idx = 2; + for (int i = 0; i < cnt; i++) + DisplayPolyPath(polytree, ref idx, false, ""); + Console.Write("\n"); } + 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 ///////////////////// public const string clipperDll = @"..\..\..\..\Clipper2_64.dll"; @@ -164,19 +192,30 @@ static T[] CreateCPaths(Paths pp) [DllImport(clipperDll, EntryPoint = "BooleanOp64", CallingConvention = CallingConvention.Cdecl)] static extern Int32 BooleanOp64(byte clipType, byte fillRule, - long[] subject, long[]? subOpen, long[]? clip, - out IntPtr solution, out IntPtr solOpen, bool preserveCollinear, bool reverseSolution); + 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[] subject, double[]? subOpen, double[]? clip, - out IntPtr solution, out IntPtr solOpen, Int32 precision, bool preserveCollinear, bool reverseSolution); + double[] subjects, double[]? openSubs, double[]? clips, + out IntPtr solution, out IntPtr openSol, Int32 precision, bool preserveCollinear, bool reverseSolution); + // DisposeExported(): since all these functions behave identically ... [DllImport(clipperDll, EntryPoint = "DisposeExportedCPaths64", CallingConvention = CallingConvention.Cdecl)] - static extern void DisposeExportedCPaths64(ref IntPtr paths); - [DllImport(clipperDll, EntryPoint = "DisposeExportedCPathsD", CallingConvention = CallingConvention.Cdecl)] - static extern void DisposeExportedCPathsD(ref IntPtr paths); - + static extern void DisposeExportedIntPtr(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); + static readonly byte None = 0, Intersection = 1, Union = 2, Difference = 3, Xor = 4; static readonly byte EvenOdd = 0, NonZero = 1, Positive = 2, Negative = 3; @@ -198,9 +237,9 @@ public static void Main() null, cClip, out IntPtr cSol, out IntPtr cSolOpen, false, false); if (result != 0) return; - long[]? cSolution = GetPathsFromIntPtr64(cSol); - DisposeExportedCPaths64(ref cSol); - DisposeExportedCPaths64(ref cSolOpen); + long[]? cSolution = GetArrayFromIntPtr(cSol); + DisposeExportedIntPtr(ref cSol); + DisposeExportedIntPtr(ref cSolOpen); if (cSolution == null) return; Paths solution = GetPathsFromCPaths(cSolution); @@ -217,15 +256,59 @@ public static void Main() null, cClipD, out IntPtr cSolD, out IntPtr cSolOpenD, 2, false, false); if (resultD != 0) return; - double[]? cSolutionD = GetPathsFromIntPtrD(cSolD); - DisposeExportedCPathsD(ref cSolD); - DisposeExportedCPathsD(ref cSolOpenD); + double[]? cSolutionD = GetArrayFromIntPtr(cSolD); + DisposeExportedIntPtr(ref cSolD); + DisposeExportedIntPtr(ref cSolOpenD); if (cSolutionD == null) return; Paths solutionD = GetPathsFromCPaths(cSolutionD); - Console.WriteLine(solutionD.ToString()); + Console.WriteLine(solutionD.ToString(0)); + ////////////////////////////////////////////////////////////////////////////////// + + + // test BooleanOp_PolyTree64() /////////////////////////////////////////////////// + Paths subject3 = new(), clip3 = new(); + + for (int i = 1; i < 6; ++i) + subject3.Add(MakePath(new long[] { -i * 20, -i * 20, i * 20, -i * 20, i * 20, i * 20, -i * 20, i * 20 })); + clip3.Add(MakePath(new long[] { -90, -120, 90, -120, 90, 120, -90, 120 })); + + long[] cSubject3 = CreateCPaths(subject3); + long[] cClip3 = CreateCPaths(clip3); + + 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); + DisposeExportedIntPtr(ref cSol_pt64); + DisposeExportedIntPtr(ref cSolOpen_pt64); + if (cPolyTree64 == null) return; + + DisplayPolytree(cPolyTree64); ////////////////////////////////////////////////////////////////////////////////// + // test BooleanOp_PolyTreeD() /////////////////////////////////////////////////// + Paths subject4 = new(), clip4 = new(); + + for (int i = 1; i < 6; ++i) + subject4.Add(MakePath(new double[] { -i * 20, -i * 20, i * 20, -i * 20, i * 20, i * 20, -i * 20, i * 20 })); + clip4.Add(MakePath(new double[] { -90, -120, 90, -120, 90, 120, -90, 120 })); + + double[] cSubject4 = CreateCPaths(subject4); + double[] cClip4 = CreateCPaths(clip4); + + 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); + DisposeExportedIntPtr(ref cSol_ptD); + DisposeExportedIntPtr(ref cSolOpen_ptD); + if (cPolyTreeD == null) return; + + DisplayPolytree(cPolyTreeD); + ////////////////////////////////////////////////////////////////////////////////// Console.WriteLine("Press ENTER to exit ... "); Console.ReadLine(); From c5a0cda5798dd0f4f8097a61023d021eba34ddd3 Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 29 Oct 2023 14:52:59 +1000 Subject: [PATCH 66/95] Tidied up C# test app for Clipper2 DLL. --- DLL/CSharp_TestApp/Program.cs | 294 ++++++++++++------------- DLL/CSharp_TestApp/polytree_sample.png | Bin 0 -> 34165 bytes 2 files changed, 141 insertions(+), 153 deletions(-) create mode 100644 DLL/CSharp_TestApp/polytree_sample.png diff --git a/DLL/CSharp_TestApp/Program.cs b/DLL/CSharp_TestApp/Program.cs index 811c3da0..0c730308 100644 --- a/DLL/CSharp_TestApp/Program.cs +++ b/DLL/CSharp_TestApp/Program.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 27 October 2023 * +* Date : 29 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * License : http://www.boost.org/LICENSE_1_0.txt * @@ -8,156 +8,137 @@ using System; using System.Runtime.InteropServices; -using static ClipperDllDemo.Application; namespace ClipperDllDemo { + public class Application { - // Define path structures somewhat similar to those in Clipper2 - - // nb: We don't need to use path structures that are similar Clipper2's - // structures, but it does demonstrate how to parse the CPaths64 and CPathsD - // structures in cipper.export.h. - - public class Point - { - public T X; - public T Y; - public Point(T x, T y) - { - X = x; - Y = y; - } - public string ToString(int precision = 0) + // 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.Count() / 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)); + int idx = 2; + for (int i = 0; i < pathLen; i++) { - if (typeof(T) == typeof(long)) // ignore precision - return $"{X},{Y} "; - else - return string.Format($"{{0:F{precision}}},{{1:F{precision}}} ", X, Y); + result[idx++] = coords[i * 2]; + result[idx++] = coords[i * 2 + 1]; } + return result; } - - public struct Rect + static void AppendCPath(ref T[] cpaths, ref int idx, T[] cpath) { - public T left; - public T top; - public T right; - public T bottom; - public Rect(T l, T t, T r, T b) + int pathLen = cpath.Count() / 2 - 1; + if (pathLen <= 0) return; + cpaths[idx++] = (T)Convert.ChangeType(pathLen, typeof(T)); + cpaths[idx++] = (T)Convert.ChangeType(0, typeof(T)); + for (int i = 1; i <= pathLen; i++) { - left = l; top = t; right = r; bottom = b; + cpaths[idx++] = cpath[i * 2]; + cpaths[idx++] = cpath[i * 2 + 1]; } } - public class Path : List> + static T[] CreateCPaths(List listOfCPath) { - private Path() : base() { } - public Path(int capacity = 0) : base(capacity) { } - public Path(IEnumerable> path) : base(path) { } - public string ToString(int precision = 0) - { - string s = ""; - foreach (Point p in this) - s = s + p.ToString(precision) + " "; - return s; - } + int pathCount = listOfCPath.Count(); + int arrayLen = 2; + foreach (T[] path in listOfCPath) + arrayLen += path.Count(); + 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[] path in listOfCPath) + AppendCPath(ref result, ref idx, path); + return result; } - public class Paths : List> + // and to create cpaths that will contain just 1 path ... + static T[] CreateCPaths(T[] coords) { - private Paths() : base() { } - public Paths(int capacity = 0) : base(capacity) { } - public Paths(IEnumerable> paths) : base(paths) { } - public string ToString(int precision = 0) + int pathLen = coords.Count() / 2, 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)); + + result[2] = (T)Convert.ChangeType(pathLen, typeof(T)); + result[3] = (T)Convert.ChangeType(0, typeof(T)); + + int idx = 4; + for (int i = 0; i < pathLen; i++) { - string s = ""; - foreach (Path p in this) - s = s + p.ToString(precision) + "\n"; - return s; + result[idx++] = coords[i * 2]; + result[idx++] = coords[i * 2 + 1]; } + return result; } - // Miscellaneous functions //////////////////////////// - public static Path MakePath(T[] arr) + public static string XyCoordsAsString(T X, T Y, int precision = 0) { - int len = arr.Length / 2; - Path p = new(len); - for (int i = 0; i < len; i++) - p.Add(new Point(arr[i * 2], arr[i * 2 + 1])); - return p; + if (typeof(T) == typeof(long)) // ignore precision + return $"{X},{Y} "; + else + return string.Format($"{{0:F{precision}}},{{1:F{precision}}} ", X, Y); } - static Path GetPathFromCPath(T[] cpath, ref int idx) + + public static void DisplayCPath(T[] cpaths, ref int idx, string spaceIndent) { - int cnt = Convert.ToInt32(cpath[idx]); + int vertexCnt = Convert.ToInt32(cpaths[idx]); idx += 2; - Path result = new(cnt); - for (int i = 0; i < cnt; i++) - { - T x = cpath[idx++]; - T y = cpath[idx++]; - result.Add(new Point(x, y)); - } - return result; + for (int i = 0; i < vertexCnt; i++) + Console.Write(spaceIndent + + XyCoordsAsString(cpaths[idx++], cpaths[idx++], 2)); + Console.Write("\n"); } - static Paths GetPathsFromCPaths(T[] cpaths) + + public static void DisplayCPaths(T[]? cpaths, string spaceIndent) { - int cnt = Convert.ToInt32(cpaths[1]); + if (cpaths == null) return; + int pathCnt = Convert.ToInt32(cpaths[1]); int idx = 2; - Paths result = new(cnt); - for (int i = 0; i < cnt; i++) - result.Add(GetPathFromCPath(cpaths, ref idx)); - return result; - } - static T[] CreateCPaths(Paths 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; - T[] result = new T[len2]; - result[0] = (T)Convert.ChangeType(0, typeof(T)); - result[1] = (T)Convert.ChangeType(len, typeof(T)); - int rPos = 2; - for (int i = 0; i < len; i++) - { - len2 = pp[i].Count; - if (len2 == 0) continue; - result[rPos++] = (T)Convert.ChangeType(len2, typeof(T)); - result[rPos++] = (T)Convert.ChangeType(0, typeof(T)); - for (int j = 0; j < len2; j++) - { - result[rPos++] = pp[i][j].X; - result[rPos++] = pp[i][j].Y; - } - } - return result; + for (int i = 0; i < pathCnt; i++) + DisplayCPath(cpaths, ref idx, spaceIndent); + Console.Write("\n"); } - public static void DisplayPolyPath(T[] polypath, ref int idx, bool isHole, string 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: "; + string preamble = isHole ? "Hole: " : (spaceIndent == "") ? + "Polygon: " : "Nested Polygon: "; Console.Write(spaceIndent + preamble); spaceIndent += " "; for (int i = 0; i < polyCnt; i++) - { - Point pt = new Point(polypath[idx++], polypath[idx++]); - Console.Write(pt.ToString(2)); - } + Console.Write(XyCoordsAsString(polypath[idx++], polypath[idx++], precision)); Console.Write("\n"); for (int i = 0; i < childCnt; i++) - DisplayPolyPath(polypath, ref idx, !isHole, spaceIndent); + DisplayPolyPath(polypath, ref idx, !isHole, spaceIndent, precision); } - public static void DisplayPolytree(T[] polytree) + 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, ""); + DisplayPolyPath(polytree, ref idx, false, " ", precision); Console.Write("\n"); } @@ -185,130 +166,137 @@ public static void DisplayPolytree(T[] polytree) // DLL exported function definitions ///////////////////// - public const string clipperDll = @"..\..\..\..\Clipper2_64.dll"; + const string clipperDll = @"..\..\..\..\Clipper2_64.dll"; - [DllImport(clipperDll, EntryPoint = "Version", CallingConvention = CallingConvention.Cdecl)] + [DllImport(clipperDll, EntryPoint = + "Version", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr Version(); - [DllImport(clipperDll, EntryPoint = "BooleanOp64", CallingConvention = CallingConvention.Cdecl)] + [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)] + [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); // DisposeExported(): since all these functions behave identically ... - [DllImport(clipperDll, EntryPoint = "DisposeExportedCPaths64", CallingConvention = CallingConvention.Cdecl)] + [DllImport(clipperDll, EntryPoint = + "DisposeExportedCPaths64", CallingConvention = CallingConvention.Cdecl)] static extern void DisposeExportedIntPtr(ref IntPtr intptr); - [DllImport(clipperDll, EntryPoint = "BooleanOp_PolyTree64", CallingConvention = CallingConvention.Cdecl)] + [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)] + [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); - 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 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 ///////////////////////////////////////////////////////////////////// + /// Main Entry //////////////////////////////////////////////////////////// public static void Main() { //string? ver = Marshal.PtrToStringAnsi(Version()); //Console.WriteLine(ver +"\n"); - // test BooleanOp64() //////////////////////////////////////////////////////////// - Paths subject = new() {MakePath(new long[] { 0, 0, 100, 0, 100, 100, 0, 100 })}; - Paths clip = new() {MakePath(new long[] { 20, 20, 120, 20, 120, 120, 20, 120 })}; - long[] cSubject = CreateCPaths(subject); - long[] cClip = CreateCPaths(clip); - + // 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 }); int result = BooleanOp64(Intersection, NonZero, cSubject, null, cClip, out IntPtr cSol, out IntPtr cSolOpen, false, false); if (result != 0) return; - long[]? cSolution = GetArrayFromIntPtr(cSol); + // clean up unmanaged memory DisposeExportedIntPtr(ref cSol); DisposeExportedIntPtr(ref cSolOpen); - if (cSolution == null) return; - Paths solution = GetPathsFromCPaths(cSolution); - Console.WriteLine(solution.ToString()); - ////////////////////////////////////////////////////////////////////////////////// + DisplayCPaths(cSolution, " "); + ///////////////////////////////////////////////////////////////////////// - // test BooleanOpD() ///////////////////////////////////////////////////////////// - Paths subjectD = new() { MakePath(new double[] { 0, 0, 100, 0, 100, 100, 0, 100 }) }; - Paths clipD = new() { MakePath(new double[] { 20, 20, 120, 20, 120, 120, 20, 120 }) }; - double[] cSubjectD = CreateCPaths(subjectD); - double[] cClipD = CreateCPaths(clipD); + // 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 DisposeExportedIntPtr(ref cSolD); DisposeExportedIntPtr(ref cSolOpenD); - if (cSolutionD == null) return; - Paths solutionD = GetPathsFromCPaths(cSolutionD); - Console.WriteLine(solutionD.ToString(0)); - ////////////////////////////////////////////////////////////////////////////////// + DisplayCPaths(cSolutionD, " "); + ///////////////////////////////////////////////////////////////////////// + - // test BooleanOp_PolyTree64() /////////////////////////////////////////////////// - Paths subject3 = new(), clip3 = new(); + // test BooleanOp_PolyTree64() ////////////////////////////////////////// + Console.WriteLine("BooleanOp_PolyTree64:"); + List subList = new(5); for (int i = 1; i < 6; ++i) - subject3.Add(MakePath(new long[] { -i * 20, -i * 20, i * 20, -i * 20, i * 20, i * 20, -i * 20, i * 20 })); - clip3.Add(MakePath(new long[] { -90, -120, 90, -120, 90, 120, -90, 120 })); + subList.Add(CreateCPath(new long[] { + -i * 20, -i * 20, i * 20, -i * 20, i * 20, i * 20, -i * 20, i * 20 })!); - long[] cSubject3 = CreateCPaths(subject3); - long[] cClip3 = CreateCPaths(clip3); + 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 DisposeExportedIntPtr(ref cSol_pt64); DisposeExportedIntPtr(ref cSolOpen_pt64); + if (cPolyTree64 == null) return; + DisplayPolytree(cPolyTree64, 2); + ///////////////////////////////////////////////////////////////////////// - DisplayPolytree(cPolyTree64); - ////////////////////////////////////////////////////////////////////////////////// - // test BooleanOp_PolyTreeD() /////////////////////////////////////////////////// - Paths subject4 = new(), clip4 = new(); + // test BooleanOp_PolyTreeD() /////////////////////////////////////////// + Console.WriteLine("BooleanOp_PolyTreeD:"); + List subList2 = new(5); for (int i = 1; i < 6; ++i) - subject4.Add(MakePath(new double[] { -i * 20, -i * 20, i * 20, -i * 20, i * 20, i * 20, -i * 20, i * 20 })); - clip4.Add(MakePath(new double[] { -90, -120, 90, -120, 90, 120, -90, 120 })); + subList2.Add(CreateCPath(new double[] { + -i * 20, -i * 20, i * 20, -i * 20, i * 20, i * 20, -i * 20, i * 20 })!); - double[] cSubject4 = CreateCPaths(subject4); - double[] cClip4 = CreateCPaths(clip4); + 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 DisposeExportedIntPtr(ref cSol_ptD); DisposeExportedIntPtr(ref cSolOpen_ptD); + if (cPolyTreeD == null) return; + DisplayPolytree(cPolyTreeD, 2); + ///////////////////////////////////////////////////////////////////////// - DisplayPolytree(cPolyTreeD); - ////////////////////////////////////////////////////////////////////////////////// Console.WriteLine("Press ENTER to exit ... "); Console.ReadLine(); diff --git a/DLL/CSharp_TestApp/polytree_sample.png b/DLL/CSharp_TestApp/polytree_sample.png new file mode 100644 index 0000000000000000000000000000000000000000..820eb85929d1aa50438ebd35a3a5102b43b82740 GIT binary patch literal 34165 zcmd?R1yEg4v?cg(2oT&ta7%Ct4i89xpfAB4g1fs165QP(AwY0<5ALoHcbA8IU;a!_ z^;A#Sba%~EP1jJxf!vUrd(J&)@3q%jn~*OGQkbvayaIthn9`p=DS|-o;=pGrDl%|o zVt@zu0|?FTvxXxGgpT*}2?t6|CkBBiLDHYZl-(8%mt4K%cMy8dMu&BOP#W|sBdT~# z{S~&Xt&uG${2D)FRwOxIAW<}Bc2t6CF}>`0LWa}znOgArx>uTYwuQt0srs>1kBv=e zr(s8kpK0YheJ9;AG&GcxhMJlhMzNW#LJ55HN*oyezd!z_qy--Hf0glnnMM8c|BF5F z?3I^InGUV#%>9I>Efe3qmlH)Pq|GGx>C=-=+sW_WzlSt2VU)eLtsUlLlueTcYQ>am zHM7;RpFVvO`~2kn_UR6NN5Gj}gJFA!WGIEp(l{iz5Bz+csrp^0ufTDqcgFrL=O-o^ z;C2L4G_=<#!}T6+LbaOyNknQrQO|94mn*f$&&~(CNX#n_Y!4|DbzTqG)YamW}cBDUT!fnown(Ip)< zTTS^9w8MzgBSenA1p6y?ECVY4QqukQt%oPjg;oT=yOVIdcb=X*d{ z=acqbcEN3;+TxezAvG)t_YLvq3wbHd+N13ABwAU+AGGcxBVGA268^5u3c&G%mHp8& z{BIr8XO%aUEdMi+24>6S+d;u?HuW8rkUEyLqtB{cZ&jyjW%X7sJfqKYTI)-$rlVx! z)A!xo`i+SWv5)#l1pAURLu064GymINMtj8i7&J!5~%T}J%KX&$zy;rN? z&}qbyx!S>vNlKfyGJ$ROPZg?!$s{mx*sY4%EH|6i*{G`%e+RQg5Q3Q0O8)>$kT{Bnf)dANssLJCS_%mdN2D|zNq;NggeoP~Yicw5 z6P@VYmu!%q@e`J2g9Dv>DtCps)10viuB?8eFdtsno73T-7K+9}b%3#l`)3TKm9VO((5`(?x0px-A0VzI{VP zLb5+z#nX`We64jF9pXG58JV?t zXMY?6+N)R8+}!b2^OYPv4{k0e%hm;g<`cBF)=V?(M;e7Aj{xG_0$E0==&YKxb!Xzzi^WdDT2F56x~L?oXWC*$j~r zS?|wNY+^3=&(S1gh3?u{>djHc$2qDk=A&`D(MeJK0uaKsA;|it<@Sk3+`<>*b&YqE(RHSAcTVJn`Ey*-6HC-ahl^PN%5_7mUY z4<7n1hX|_?^v+h>BL zl!W5r)2iKwZ?v~J-?Bpq(x@`2EKHkv`WdEXu@UxwfVjeAGo8e&F@ftJt6ZW<&^{4X zY4Fqjd)yR_Vc9XTXBDR+4`$1%oc0tM)k=qQWs^Rq@suoi3?`BYPue!01**7b3b})r z4SxbT0TmscMy*u4zOhkr+Iq2`0~AKYHuE~0lGZoq=4R6tPeM`>&Tey88uW@_$Wkd^ z6pn_QjNgH?+j_3n9S!BGm|Cs$uz!Z%Yx7g3^}FP`!1En8Eo6-tL>=P652Cb7o=`@b<2b45tfK{y?%sZqJq1 zAI`UHgdw-Lw;E60Fr#(;nlDt)bBzxFkew--1vva4n-a1a17UyvngN^E)LW&>t7iK7 zqj-2eG3TG_iWDgUij9HY?cp)}x*Ver>Zp>qsX(Va>$0wiJb8rnh_qje&E^<#UiC@n z?ZdCUS7DPO7A_X2bY!} zV+;dgX={MYG@r>dnTxP`yws%K=Cf2dq^lLTP4`M11|LDnMeIl2t!FyEPN3cF_H&Pm zaI9H{x5(q{%)V!O{-e&MWC{l|11sy(&)N^H1(=cjbyOlE-{E6~BhR)c5_4Re#L-&20bKi1I>C5ztjHC3? z^rysHM$~@ao@-gvxMjBXeMnTMByiqqNxVH>;a7)eg!{G1U!Ay+ey(G+K0QCL8S3op zB>wC@I+FAI`k2HxGxNB=_Sd(KNFHmqse9kUGZ;5{Zd7=vhY_hz3+{o0B%L5zlvK6K zCtX$=$?$CZjh;`Ek`vit2(R$pwf9o^4*mU_gB#@uDQe3(Os9z*h#fJ$ihuVNUQbW2 z2n?;W^)0NWuj$DAlgR{uK$@GIaYuEo57S4FgcRE=}-104^qUw+f@R&Zk9v$eSFx9l7+d3V6)jnM`Yqjl5*Y|EPk;Spnn$P_% zU7l3jcFCtOi^lVc`Ec1WIy+lGP0LBGEKq$RnkT~5pvm8Q8^>X3m+*QtQ$jGX58QV+ zU&Zh9z*nSJ)-yaD0R*x*9&1fx%r{0P!G|X&wr}s%O6M~3aR+1f=Nuny?9(bwPfznL z^4r?LQju?|7#Uyj^70m$i;0PWfP)k(?0MDYIIEK8PCG5@u`dT~RuasUv3QDYNQK~0 zR|uDOz0=Rs4~&y7mRhp64US2mzgL>>pusq8L07W!l-?1Yd$@OM+XLW-5(kEPAnTgXVl!K|q*SE>!Nf zNFNzU=$Xutj;c-qqSzVVibRoW3Aw=P^#jeL}0^V5Z^OC(6Y- z6|UT9kFlt6qm@$xT58Q@?G@u23@p1+m5F@Xata(mI8lSk-UL$pbMV6tG`!+W&L*#0 zP6kLOEwJ8E%^$9Wsx4<<`<1oc4HSCygp+`(ZC92RRey;i0l3U+nJE53!?O$yTk9?q zQ(D#Q6CyO&$A^tj6u69-jceVu`%F_F@Kqc&S87^a0;VsLbnlTh5g9u-_*Yg<_=Y0@dMuq1g*2rrU{RvczyR>P!!aq`nuDa zAIGIV=;c#?1Oy-e5bYZf zC4reoO2^S_h;Vt`*hxmdmHrNoL=5&3sj;3jI->%L)QBI4Ca;}{@$ z&IdDz&p|(asP~}fZYIv<_>TdArr>K%pb8|a-^gHRBm@7w!DC^x@nGV3rLk;LrO9wA z2LM@{qE^HF$9xB-3OeeWnsRT>4WF$3ogDy$#&(7IJGgqwbf?eVIZ|1|>a(Qxzo-d^ zd{dP0e`J1U#!j3YnTjePO;lgOvzzvzgGI@RncyKkGLE<13H{DFv447id;9inUrY|) z$VflM*w?RX>0n=M1_mNdV@A<@1r6){!?}upHyF9Kj7C#^G+U_NL%%2C zG#!!K9!jCm8tv%#u9z!hp2LLNA2!u!EW51G40&?BSz9z(7_yY!cPglXNo*F}n)J1o zh3FODQFrzB#_`(fwm!gQORSKnVs^@U>qj)vs9_Th_V#N*6h332H$-F65F8wwuh_W% zuZ^+Ke|P)LS`R*i1h%piY%ImkAPJuL#;dp-=nDCSU|INlB0I^OI5}n0&XHxv`D6YdW75L9Kfb+_$3FDJn#N`d)j2kbNEw`gz)_>)w|$4`Ly-px}T(^EI%@$hRhLB5RIm}FMsQ0m{Ps*Cr_)m~- z*>FV<<-OuB>|D#1e<=YwyK0{7T1hA6g-sW|q9V3jiIzkk->qb0kQXdz01xk>x>pwQ z9tPj61QRBN+>k|0ktOU#a0r~mK|?M8+ELbwN)byJblE4V@N?Pw}l z0hi*%;S_`&W@oUiFrgpY^INfqRuzr}&Tow7@Y$BLJnwBfA8xbYP9LvVs_Zu;w};bu z0ZUOAQNz67uT?3Ki?kS*d5PtI+%$7fCDE`6GEq!TUa&ad=!LS4==ch%&zz&(GLcv* z$Iltd(ZmL-7_R^or&jdv75I$f5V;b`E-i0F#H}4xpjV4Y=yu)y8hktt=r{WPZPo+y zcF_UR?^F;co!>2z*_~7AuiW>0uFojP=E8F|mZUg1Yws;O=c}6F+;%ctnT=apTjN=+ z)7e<{S~1%H{X~Z&X4eNTFE1xH8ukzqDdk8-%^<5_<5nc_=Tm@sU9&0~!QQLPestl7 zI-VduHOp25t|-N_B+#nzx~bwqV6^Nki}5xh*DPxwX!UR(qdoJ(Js$MS#Qr&Xfsmb@ z)9OUX;6gU_=+`{$o(lK%we!xWKj$BjX|SyYFhuu)BIW(6pF}`bON~?#)}OPMDg3+0Rgd}@&hEE7jM;LM z657yzOms@rT^!8!2lW~3JgjE+kof^gI`=Ooi`JXEo)?Z}6#P7N()O&7z^D=&67owb zioCvw^c?ICqXoGGbxn#?sdhuSi|$PK#9)tfk?7M(bF(l>jT3Re8}EVeQBy5>(pJ0j zt(9+e;WpL~TS%I_Tiz9ja^2-q=)#w3gKx{^bN(xmqZ>uXVD$m?gnr0itLLT+E)rfW@P7Sg&U6&Co1=gOjMrfVV zm6Q2QF%k){&D+Pj%N*0u%tV;iEsVoq^Yh@d?NTF>Xezh4)pm?i(c#q*^ydEFva|IQ z!2dnFTADm}XG>=JqaiEob-I%FsC#@{9-16?Xky$8K}Qj!`q35lDfO`)sHS zL|I`G3)d4x>Qssx%jNC(SdFCdhWk9;#R$6EBrBd3e4if%BcRgB!yE#(me)i2OQ~@tCU>l-Ma`h%J`2UChqrByR*<&t%j?uqVnb~OfKrRoQFwYFX>jaq?A;Ax>)BXW zOu-~IS>4|s=mvx;$&S|%LP5KxX zBk_}L^E~*hYGZkr%X>Yv{BjULgP8aB)=O2~V(Fq(k`!WtIf#-(g$)!-- z#r;&ucYz?>V}EB8B!>8Ihel$@}!Hr(Zx?1bbZBCm2JcJrlT2i`^sOBhl9BjN#iwuOE!^OzQjH-92~&I1&rt=G7GvnX?IvIXFVc!X3B2)Vu$I-pU9O z3GI#;Z~V)(nt#>r5xwF8kB?cW6x%~=J4TPP;(>-WCG*N;I=IOy`kyQJYI5M0(*WDe{eTQF@e%IG@ z)Hlki42Atr=fdd?XXxhSmWxud$l2+6Yf0}`YR`FLd2~I;6L^G0vtG_YF-o1u$0vyG zP9Iy}WY3rDOzqurzXWrwU7~`?(MIYYTC*1B*6EBcfhqAy~K=xYO z+38K;GG(qC4#Xh&S8u-oux~6BUZ-$C8Ze$I)&MQm+amzvhqXFDk+LEEx|L02citZA zRcl7r{%GQMdK{0H9Gbei#xPz+HzU8`sMD^zh=>&_7LIZ*kmJ91$sk9s*6y{4f#u_B z!Nb1AJO6|9T$)SUEt^u_fOP7B1*Y9{ecXgUq8dF;)>avg0{`yRLIhdi$k2VwB6i90 z>dgJ1;**3V$qp*RyM=N-OZwyL!lVyH4EFZ+5d0t1G&!F$Jh+w`ovScBkj*CZFgJRm zdZ(wS_qhPRbG>R*r0M}oN$Ce5qF6dvLhG@l=pNL5^vnAJ6^QCMsT={`q$GS)t9crZ z9Ep67G;2fn25O!n{g7uS^^Pf=65-_I;DyvCgOUi|rl*%Mjyn%}B5UsQ4^6p+Cro`W zJnxBQVNaRqcO9{-8V>@ltZl;?DDSW4tuFiEeJeu>90*hp2G44cgqlbBSc+}yaO zr^niMW{U+7bDLoUQ-~^N00=mWbItL}>VlaITMbohtG5&`ZzNLa>CDC}T2%p3yl==E z{q|tY<-V<2JM(ZhvENAMin`m1XR^W&J@td^f+GbNL6@g3rO)DU%ZrIPn0MIUaV zi91iZq%2z8W1&R1C^D?5!dUciA+6rv-`_e-G{*ta_F4Cp7&ZJHSZa2c~8yi{ou#JrkG_1Dk zPtKkHr~yRq6A)zR1q8M$E&;Bv!Icg6vzm&NI`qfQkKUQEkZ>w0Rxwg}{2!k(Rr@Z} zrv6?MmS(#5MHlb^U;fUPw=89!^_%Lg?WY7ek%X6vce6kG`&K21~|6jK)uHe^Nd?*9PVsh#;B{tlo0#88M4?ys!;pZc&^%= z!M4_-t4_~XJU28~qsoPgPIk7YQ1CQoJp9f8p5MX9crm%7+IljcMhX%u=$mWW_-47* zn=PS;hk=jp2i#*Lr^$tsTfRc~9GkCEdwYzkvutANVBs{W`P`~=>l@y9&fG9A+ozMM zLZ~LGQf3&S`{bII{c>}6XZP3?MfRQ}tD2WPS6!N`HxJ5Pu8AhaXH!WVOe&iwGL@3_ zd7`qhDfJrjz2OS_b?}|{#&KKo6@ciX>@y0^R8>kd(esr0uj`3i+IpStLv_OUPYFOr zK`rtz3rJM2hnj{Ln+nL4q%-4GzX$(PTY-RX?Ek%6sIz=6I_s*X)L_Boa=17;W9|74 zP*rULm$wgWeR}}yERsSO;7H(CrxoEY4nM>LPizd(tnV#8E+HR*_#E^x-C>)cY^@}o zKQda;w!IMJi@xuCe0=5lYZL&bky2}&%IQ8q1iILrNffw*R@ywO)}G8s&2!RKf2Hd# zMGiW>?K=bJkL~5oHZsyv7SjpVu%{@Wc6>n{LS=-U`dZ9!yX!vb&x?9sK8M4+Cxm1F|$^uRU@M9f*A zM28614BcY^wcbA^;j=uqcjPQK5lN@b*s6nu7`xQObVZW#e4Fk5(3bnxfW+6yL>C4V%etr9-JiAu#WQJ5n2-}NC7KM?OBX*|rVWcce4=jW zNT9JHxA`O`>1eS+ve>fNufxo6gxu!-Ol@$E0}54PH4u!dZWo?w@e~9KpUFT1AXs_6 zrr`gE##wVv^X*X&$~?(w3^N`KlrZXFM`@q0B7*B%%NpiEH69)MiJk-~j6Ex0%g}|@ z-Q8flM74V7`$%KWM~~?gK}-MXBP1fC9ituRjXcTVfhvB73tB+nEB!qUJ<*WAH<+UI zXnT&d7fNq3dW*$>_!euP6T$9yu{+<^Z~Qi-stynA%^lGVX0A&Wvf0|fXEoK%M$AG- zZXaElHQ}M>#WNWKWEx#kmBWb=4xH7yh%cZ(4AS1aw%W5>)5lxqySk}Tjh{toANvx@ zes7r_qS@Kh38|ci-rd-+0xmORBA%o^sLo*(aU-Ra-d8&0|yz zx8-R7C=mudLqBJF12P`RnJSotsFCV& zakHkP8s84DQzTB-YYB^_E=S#EL}cdr)Sx(CHjk4!vkke4c61I3&ksJHp0ZmjSI7#B z!?GM;n;O*Q9)(P%BUWAAOTJiRdmE(viEebzW)0zGtED)JFg14f0p>#%@;fGwi# z0K*^T4_h(Hy~}J#k0_A~@hJFAX67FO!8Tp61xZRO=Q>~hfcGVC6BIqIPvla{LSqx@ zVAM4}O=wC!jn8+1dGh;TvOlW`xIRV$!9g<;&hy?--2aRI>XGXjCEJpMqi4>}k?k?A`?0HStovjl9mH?UvSBs)X*W)*8 z8RnB7P@ShELD%H&ivUbTf##Y;Y}Stt&9LTfL>!>S61c z5Z;ay?eSpYHrMh)Eh$2jUu^Xzoc@DEF=SvfYVNtcO;|DO65wX|99dtu_wbJ#$E*8 zZp-H!h62&=LWfY_ACvi`O8W^0s?LD)i6u%8)@vGcb#>erR;?B5o9xW+x^=**cW7DS z`Q!T+69_IZmX9gfJAQ}FzQPEWQCiCxIqg=j1a5OBs4)FVX3J+IW&B&%7JPWeBbF>9 zQ#9DMa?pJKH{2eVdQiObeDhq>N%R?oV1~4`u$(gv9qrTKkppdioy6(Rx2>8x-+Mlqz z?@|>!?L$aFkc*S_tf-Waym5RixzzZ+iFet_4YM|#{dlR)W_M^#Xq`bl-BYApjNUx&DWSUav+V~IH@Q!ZJ7O?GMbJyFK4IsGk=GXGAA6Lb! zp*_GbPN&lGm!GkJ#re9)mXHK6$hBVE4>9OYOe?dyj|J+%ck_SWZbaq};-o{yV+$|6 z=4+>r+oCAkd0lVdnjWB2(4pLq*ea@SfC__C;y=Jf)MqsL7e8vWC{l03Jg7YP2b*^E zaA3G7{s@{3po1#LQ#IZ98u8#+tM$W%uxd+acy!S{GZkw4^z_f{u%^ASQlunSqzaQG z6I~X9_QZ&=VsUKIAeSDb>6}bj&uEA|MPr0_&_xPRqIBB;xp((e+Oh~6F*>Il3_c-& zoXv(hn4RO;=rm%IbSQWKh)b*DDHPhDVuP*lRe<+$zrW8yg9-PO2ykQzg%Yiz2n}DC zHvc$Fw*5g5nk|esKFI5iDmmHO>iC03lfwONljb0ToIg4QJkrIMg7WIn z{KiJ-O{>;*#bxDY|IpvuEYK;m*&V3UN_qYOWsZ>5(CM1d#pR<`qVuI3JA&owjVfb` z4L!al)z;>z(w{%r_4}fU8pHEg8oxA|5HvRnRsVaz>d#a;Wo)f`lg})JF!`*^yQ zG?oC>W>ofX4?B>@OD<}**dEl7=3RDRSq*++)BaBxPa*$`RVe?ffBBp8|I!=^i{)!Q zpzRiyL1U^e*m-^r)B3>yV@N2gwUm(qpK;i6v)`fTmL zSftP}TVD)w_oLnQm#!OX)~f0xt4xo4 zfRbJ|A&;Oq??|IAt3y6eD*;KF_1{id;QHp_U(B1ga!+n=0MI<5MZuFlELBpRmm%o& z{g~iRGr_n&E(JIy|Cl0X9W6hmz#{7H);ODL+49xL{Z%IcDD-)!ltMG1#{3Z({LCjw z3eoidK&gAkPwm!@x)VsXAmDVGtoVfWPrTC%e8EC*^B7u7QAf=w6blf#-xS}2B;J%E^~s1tjmI!2ezsjM;>fEf{$^>m*%%(N)Gr}JHv3p?gyRQ`hf{tGeYMu zTb-uiWQ7%4LGOtmB{rI88YE5UH~A=sKfO{wmzyx51v0_m|4aop3Ew z(NWvRFZquLo6PHwYx|79bM9q=6hfZ!Y=Y&r%CTlYh9493?E;paLvMx)k`51!RVI`5X#q+ zz(9fhXyJkM8$xtJpSy`)D53??cs$SP#aRTr6N9B~=o1*+N_lnn_XP7R+p7GGRY5qMY4EwF2a#0% zsY}_8>+k*RxE07__G+=gJ}Ks?Ck9Z3E(K?6wHVAX)bED&tn&B;NIG^U$GD20N!eYO z9YpRo5nwx`)^!89vSJ{k;n;o?%{)0tw>`Sg(4tjdLsOIHG>*H*-kE#MbASYR zP6SE0$=dnKmal6&7itbZw65G5gx!$Po$IT6c|GK?U6m~wZN!$S8fO+P7EIS7uQc<1 zznODuUEw>9iE3k5Vho9be0I5&7RG~ZZu?dxhxM{?xtaJX-v9CFx^t~YoQoQ37gk8pgm;0?BH%W6%Mb^I(%eoYSLp2aN&>m8igr%x z!I>q{1GhWje3j`p(Y8{fKK;eSCilrJi707fl8Jk>&QL(4RzDDKCEX74aGbcw11x8E zry8|w>i*&>B3^`%3a>Zc9YSWBQyRP&gX@#|(zTB%2BZz8yf|~^U2cJWpEK(^Qzr!E zudrJA7nbk9 zH76MPR~b}NzYebh#!BqcJgAav)%~p<7G>Ds_0SO~PW_C30QXTq9o)GxE{e@+z8kPD6Q zqEm-U8LsCvIxF-=383&|zH}{{M-jcfGmh11jndP!sH?L6X!I$5iHA9>*}uKOcfw~5 z8egFEYQ>5K)E7S)RkY(DQtyd1Iz^s%mqtihw=}@}t|;OW|3%Gidb%!N8XXPDSE`bC zPIW4S}0n>^UF|TDq=n@2mhH!BS?N8#+9rk?X83MMEA_5m5;Fp@=(BuUS5^q zICSok@HOwfgC?E)mU~phFVVh@LKsrdA|sh}Ib*?7YWyuP$#Z{FhEp_0Z%? zCnVWJAO1MgMw{O;Aqxe9lI;SWWpSPzhV}tIi-~esoxzPu7k-8(xK~62rtcLRF%x-h z6^dXT6qlRlF&lex4dAv1t7d7B<%gB|RznJjFnmf`La?>^3qRcX^f8F|qMreB{R?&D zfZOGG{v(uCr#PPg@%$^-Nz#$++u;zlvkwH^mQUVxUs}0c(G5mpo%QB@4W5bdzAj#g6zTM68II560Zz zTCXrxgcHf-Oz`GN?yX+2i=ie53}K`HK~_@0t2ax_@5+XVe0vHfadY=r#uDmw9QXJl zD>LiUo#zt-(NrpwL`#&{72BQu0%)F+VIuO(hV`-B`>IjizGUqjKTneP0krMzuv6|4 z!0iY4>Hrt>dXo9MdbJx;%-3<{!W%}4i#`9Oq@e!@`@vL<0s>o7dpWGDO5Y3m3_aYN z;kc5U6>lEx3vi!ldx_*)?`#@(0RQ|1BkHZmU^^5+i&m=*IC}UcH@jMyWy(wnUfS?a z@87oT<~ooRDl7ObY6M-e?2fhm5B zTut|Me(xt94Ya@j+QWY_KHR6&ym|@a|IzgGe~bVE06zZj#?3a-mGO7~TXPG{b%IFz ze^)h{?G!YTsAJPTuc@qC;WYYbN9C`Ro&ch9%gQmi*@|)+4UxA&wZ1;>?;%^j_Ar z^=-Mc0g3mj1;I;=yaD1GytW8+Wh+!_dDCUZyZmsr#7klP*yE9dHZ(smG+ieDY^&%d}GAaobe~IQNk# zJV}iNUfbyi<3Tm8C$PyPZ72%9!C+4}zkxWy-_-Z?pFg)>Pv!Yhp@-`!Jt znS=sc6;nV~7|OUrh#;MoxeaZD%Lu!uW=sW0Z<5NqonEq))#EK6aJjr*=L`BS6f7Nd zeQ>aY_jw|z!3z_DTY7_sLpp2wLsNY0MAnx|V4eFH6^i+ECDqQTUY>c_J>1@Ig;MkN z6ZYbwHovzQ%{Wf}3qJ`OP_u|#=V8`p3ScRo&VzHEXQ;o)Dz=&O7oU%|k%L5;lM@^E>Zw?ROBruH`4+ zbfSU=;%z@`YYTw@m^)_K^mY)TIJ?5eY$a7m(K>hUELxDr;{GgSda{cdHPbQkpOYWtpo`)~J;92X?fhrz4@E@u*>wG{ zX5AcIP-Z5%T-F^yWaR6Ins0(SX+xMDng&k_xYKhF9?1V3Y(j& z?`1s!yxWL+O$rT_bR>M)f(iSkj0EJ>V1YRUe%ZZtZ>HJmm zQ!J{*lpxRR++tIMoBYSa#4?>aBtJko(;9C3boQ^-`d#W=U`x>7OOGun*0L`h&CIFK zIZNhMJ8y(VbM-Jm`YTT~lBoSjgL$REi&Ul& z(1F^h`|nZOk}w96qzl>?r(4Sb)@l+B&575{OtLGWRVe`dmHWMty^n$}LRGqK@7WS< zB0?UdG6Y>iu#mlMRJH+DCtRyj3te znxqHW&gqbZ40W^=^~H9dgos?%+qnUpyoc{;ib_gQP?~%TP$wbtlKqFx6tFmgZJv?i z0xFg>bRyh)yJ;z0v=`HB;=+K0_}QTp5$RlNRWl8S5-W zJo-v5MrNzc!!5S@1W^;b;_#Sf;1glT7Lxa{H6#mKtWA+Oa-#+9ao(CS5COS4W;5QW zhEEDh^I6=*uHLuq!NLxe-ysQQ_3>JO{VFWV8bTS;mOh%Tjf7Z{^mQfzCFE>zpX{j~i5u`N|3fLpku6GJq-AGf}wmBX?dYl?8| z9fVO{lh(lU09Z2*nPsqTnD0<0puunwbGrWz4PKsoBPM84~jYi4*%H; z00Mgc*yoLbeo(rmWsU&%I1)ViWwPn-07JqcnB{OOTLmyQ=>TWpMRy#OnJ^i_qin7E zRj}bDInbKUUrqmz2!v+vc)~ab^;c5R2{}yXIn#C0?Oj~{bx)I+nwsv{6-R~@ac#Yc zrv+9|CdsldSo*O)emd#$J2Y9)mcL-q&Hg=W@X8G7$#lx)q~et=SbP>sD~`$cO6p=r z^!atq4ILJn>oO^OmwIa$-pGgo-~)_f{is?RfVldqX%4=Q!?7w|>l&hjRVxpgn3W9^-8U2~XfbNi!mTSh`>cfe|$oqSm zBbFpjt-yB3#iI}wmYqXv9DT!HT3pxmUcY{?OHN=a3)*_xZ1+b_Q*Imk9&kkQda)rgvW_BR+U3z+pqgJ`N!wnmrr_ibM| zCg(RRbghBc+odz;B_JPIx3E`C-3w*9s zdhFNgATSrI@ZT@b?8w)bj4J%;#3fw2DLIlf{-AbsfsEh#To^n~~nI~gHmnyJpLx5BXCtiMjLYEs7K^oYi8C!Y&K11cL zFOceN`v*bmrM@6n)fc4%`v|TT)bOq?QJ}s_Niiw=@X%`&z`Eo~8}y4oe=p0jK)r%n zN1$#7j^X83UOXE|k|RCWS*o|uV;;;-jR1YT6A?x@IJY|!yjK534f9{tGyenMzr3&i zE+5u^k5}vEXZoIlzL}B!=Ue(m^L+mwzQ51`GodxY3rP5{yj}kfT)9QjI9JelkvLtsN&2xt2BKvuGsoI}-61 z3mzr8Zw{!D0M`(W7(2!wZ62#Gr7QK)QMwfCMC$m!b#+yZ^0$?u&E8A0rkz%`qbS43 zj(b+gufO~USW-l;CF$Wk@q)}X48DjV{|H`Hx-P+aDDQV3*W6p}U;Vw|Id9bn_T9!% zwo;A^%NHkEeBvU6Je{{59o|}kg&A9iO92?JSbh|x%5q=I(kf7Dqk-0*{BkK_clY72guZv5)N&-oe?Ha5beU+lCn;(9Z@;Jy|htoGG{U1LA! zVXs&};&%3fR(DJBnpHM*M6Axx!b*pb_4}u`(gi)fH?mEwAC-B+W4mwglvK5}tJy;S zs0QuVdp73J&G@N)1l^c)nmHitfGDX%z9)rV8v(YlL!G)T1C@Y~CcNIAvT!8*k%p`@ znKWcSv=gsUiWQb`%e?63^F{qR&trticL}##8T<_)awG_#dUr0|XSr??;VVzwlUr(y zN+W7u@|}Uc@^?EJ2?A|g}JKF@C7fC{O4g@bU7rHXV#LF-8}_`ICzWTFhh3tCKL z>2R2WH(i|_-G&|is2dWZ*+IX3A^DM3OokWN7wML{GLl8?S#8DSV==)N!ibIx2;e`Yo@iDyJEnj=ol6e|mOTER{+nR=6jac$*MlmR85K@Bd~IJ0VSB zz>7rpb>M7_ubk||q3T*V3WIdyCj&|kGue6x9AA0|EA6a*DALb1e%-f5PAb2oV_aS| zb46_F`a%AVF7gv^6(<`GXR|4;&fGEFd)Q00+WYCW*o;j#shx0d#3y(r;dWbJYkcP& zGuU%P-BZ^dg64KsA5P~A z;esE^Ch49s6(I53sCu8eQjkCVih9%NOAJLah+5(a&y~A|?Z|I=&1D185t#|THgq>< zN3VktB|HIV{*2dY%Crkwxl~sGz{irS}asLyC2Kdsh^iuCqFbb3gW+7;|%Y z>=kaO7v}x9QUWCs)On|hRrlvLkI)tD`FD$sbt4{AW6Hn4Zs5(yS3nX2d(5g5YaU&t z$K)fzzYK;knNO6p^cf+*on|C8mXmloILo!O4q zAaU7-XEC84p_iU2Jv`xY=DwXdH9?I6p%qT!u}qy&Pa6-E^%R~xGtxfT)tIUhYe+lp zPtjmgvukfS56*jCkDcaoa)HyASk6VdYkbM)!;nzFN+k0DN8L?*%Jj+6w*tx1w(mG< z%dkOJW9G}Za|YV;m$?!)FRYLCC) z>R|vh)KA6$4n8K;#}m%s#iwOC@6zM>sTjY~m|q%{&g6vu=fhq9a^F?kba^D#RB*_y zndb0O%BT);tqdwTMY!aU0>ouwP#{)&R8Njx?>3H{xh{%IFZf;R&vD`BRR-i&l5K>0 zoL1IdPWIQ#r~@D%Sz{SgC0arvJsMc7!69J1!*@?c>25)kkc`U|*|n}Ee>%H4C(;I= z7x%|hzjczESdgC?dj$G9`T3r?2DEnd`09228Sm?8VzHx_J;2taN9@XN^?_$x;}(Zt z)Z~_K6{9~0kE*&6chv1L-VKRZe~)~%MfPFHV!aZ!7aBsadtT#PEN(-|f+Jk1H*oKH z=nQB4a+e6drMQm6<1zeFL}ISRu%32|&3?J_I_RvG`e-Q&R>omT#-0IT6xA?omvuLj znUjJzHk#hA@ntWk5Es|$zsH9`#oRkN?+5Ku6@3m;MMUV&Mg-Wo6P2`(D&G0C zGu^5zD&Iy5|J9j0=k<(}*f^fy*9c^M)^e6&;gLyL0A1 zk`jJ?=Uh4iQ_N*?agU+%+H7m#S$8Qr;%PNKVZ);RtbYR56OSi`a>WKE8cAn60}5$k zchn`-nWLV6wikUbfIB?sWq|QVvdb5dc|AyQ9vfar9ZM1Y=2q=_#siA^ol259Y+ey(smxk`mNMom<_1ROuqdhfwqQwZ2HPSPTIQbI2yE4C_9^+ z91Gi_%+vT}xQox1xr9VO%Q5XK??>ixRBEtA(*H?Oh5`jD!R@A~V*4pfoZo`FM`yFH zok3hJ>gEB3nNR>R$2Y27LK!Sa|ClAFscvE!yL)4<3@>8)UA~AKwBU@pIXW7hsY}yb zj;R3EB6}AXZxNqws`3d%I%DqapOXHFDp$-<5+e8m=Z+Va22}kH2US;>))Yr>xG;EJ#62Tx zDxASj61K(yl!~({R33Eys%?mpga3#~^@LC82Hw8dtOZ-5JCbE7{CFp2Mib7Vc+{g$ z|Fydq`;l-taNhg$Db4j8-MJ~;7}Bf`xwyHN?53;TLrIVCuJ)z$8{&R63j78nQ< z)m|%E(m6sK*)uid@~4qSOQ6OEWthWZw|tLa(M?Lq^Fj3e`}fI`YVL39dC)7R@w?pw zAIKpY!atz}i=%y<2BECGe_uL+tWL!j>0Z`kKd z_z>L4NZ})j*W*Z-uo8(p$Dmlj>$h2HBb`A^`A9CDM8HXMIJN3NN2bhS8+6Y5){PuK zwwtbdQSgiYiT!r2>nJ&eFH^vfc!y}RnOyPUgKANHLHh49C^l!tp?JEFr6nfojzO*r z$Ah*JC_#xfOMPGQk6_dir0=OOL$sxQXqWQD>j1k=ejMp|M2UXnVO9gG=rgAZ^dNtm z<E?(1&g&SeG9-u5&hQv0>v!KzYBn0)ne!FmY+K`;s>5q) z+nw5xPb2YnwS}u>dmHO{)^KYJaSrx-K06sbzuRU>*r4BnhQBWN{VB}thrx#WehGay zUy-oUDgL^jKLOv${CIDSZrb_xLb<4$kq5Hg5K0!Wz*Se@RhOL+d22bb`uJq; z^g~)s+9!MGpjYqAG=fGRnmRQaRn$q9b+ja+&`j7%`lT?fpdb)xB z>ZWxjaDM9jf`iW9k~Fw*wD~hHY)yduh*l@M&cmtHe2d9$E^?T}3)`1YB(MkBYB`$` z)4JB^S9{x^WUIWC)$cgA-%RDit_!Dv{HEi_A@PRnm+{X?axP2emHFfLecM2>OZzw< zw<&*uu%hgr?mZ32KXIpUC$xlf`| z${itC)-R&4m5V@Azm#MA9`!qx1&5Ue&?!bwRK%^MmP zv9xb>Zhz>E+#3O23MOc&{GT;c8 zX1fz5i}>h?DqU6bTX*<2_IM-(1(hWYvT-%XX@a5*u)AkEX~q*>j6^X%EP(E3|SO24u_ZMa$!#HZHI z482OJdtK+wF?YrNB7e8F?|N4FKB;}OgGdyABZ-&%#*Uq7hu23shwv&2;ghgwbExIo zoPIhjM9b%!>gwBcWcP`_U)EM-V)|w?Sr-eQyIb>HiR+jxA$O18o!67aJQ5mUjok~a zEcq+rg)@-B$Z87M&btY&7^s<Z!}Ci9}C=`>umd58jy#zGzKdki5Z zMuDjJVJy#9XlPg!>muBG{9tmalk$8264~Vg_YsQwVpzS zNelO_FX9+e<_>ab${Rh9ULA%$@Q(-d`sN*o7Zhws!C()=aq%}zC`CWai-=AVSL(vM zrsDFS;}?F@hG#$cwV){gPs3>*4;I?J?0g^-`Sm5(O>p1cPt1K=R!Wnwu4djHRi*(# z+1sCSWr(?w5$`v-b~ACX0%E#BxO8yTM114SA$eJq!Kf4;-U*IM?K?^+;k+in?bM}; zSWUi9>TT)so9NMwX7ca=yp+kR4rC#(CXlZ%9_wv<1O_N$TJMDaEXKbtQtbUDWR>rc~3+W zxU{85uH^3~$_}Gx>KGlwrq2JKzPaN4O15-$g{01iPQ^0{I|jsLJbY97ytr?#YW8n; z9HaSF_yN0UkNf-kL9!bJ2MwBCgVSk|(2Mj(rkp;~{qgAIjg>63h|tk|!^=l|ov+ES zU#~G4QBqNnDK#ZDIuEk7nW=<3n#w~wP6+I)8B|74g}tjQg2Y?LeAz}N$4L?U{Jfy; z4nNHB>J960CS}jiYV}O**)`9Tr0sZAId~s*+-;(JF21@CgDlQooi8t0bnt>!SE}Sk z^EI>moHt7t*S_G7!Pu56ovmElen_~fGYymb zPW}Tf2ML0cP@`Wf_f0)zCjKIplRH@$v%1950CUvak3pu_3w4sjtq+ITtXV?eakV65 zO=0~NsU}~;{@h`wc=58E~ zEMpOxsu~>5VJ4QIL(J`*}VO-J~f6r ze$8^Ti~*xT#3=BoTtY(76ifvc%WwnfxACm-~iKIflv%*c#k zeYL0q(3(j~`YTGo5zCmiH#;eZ8P#cYh%L}|Groae=0i#+C#NKxbU6!hPFfYX8H_N= z9MVOF!3m}PPCxOo2gT-SaJ^H|?~7}0{Chi|<%dfx1JF-RnymOyYQbf-= zB8u})W%k{!3)8%U&-Q&Ob`Y46b;eDX&64>2yv-sfBp|vogLb56!-Zxa$+PZKsCW=C z@prV5eoX5vo&iwOLNt{-jWIY<_sGp%FN&?TP_lB%ZQho|0}@L9jHL+w>O(Q3`t_&>F$%82eMikx zcWU0p2ZjArq{QN}?iE7|fn(@F0klmvwEg_kr@uVXX-MxY-%N8^*=>_1C{(-c50(j_ z!SCkeaL)%nsBsPYlK1l}AN#dD+wFFpwiaB`#NhE7Ef`3gl`tC2`VtB|#Q1a25Ol7? z3e0cgae6++HLx~%Lhdomf$U`%gf%djsiq36-u;7X^c+>rpZ=_3`)AL%UKc4n2E$V`{=P?M^XeT@;S)HJ#=*!9=ZpC|?r<13Eaqz_CA5XBVx7ghT6DE9v zrB(X6JtOVlKA>@W-Cric3am(U)E&!zX?)g{ZTW9p0B4#HAhSGf8avSxDc^qt0a>)N z`tNS8EfZ}a)`sI1b5{Z6b+V+DYkh6C$jI+T&K-v?9D$C83*t0x1|wo=LnJp28JWrD z^KgAL$*c7JyN&(YO8-W20T>wXC?VUbg;_5Fj z#w=LXE-1m-wa;G0oK%Dxfz|B}h|(VFmVz|Z+5x?Xl3Zz|9Y!T$dn0hnTsBY?Z|bv- zfqEH48TI}2+wAucZ7t3XMavWO|tsliKn{}T|jHvatj#GOsz z{~N!FnM#I?(LC~_{3qp?4#JxuX&rz5YM=$cS&@7Fh$E#gV6L6J_@c&sky+z&8UeiY z^v;chF6EOZ=d147cFqo~sSeg#A`ZAFtf_IaN`LxE3N^O&+mpsECJUI>u)VL4JJmxa z$NL4>^F7ses5^nR|9w4s0oL`Vu{JThrNzaK zRYf)@hnh==^^J|+tHh1up!rziKQdYtYT8gSLQn4>rbwbH3%SpZ;a}k*U%^e3-i2C*`{-jWyx$kRIpJ$Ra;2w3^5FP5P-NbV?Aw^Czk< zc@}P}UndfP&B)w8*qG+b_Npep z$=-eHbw9l@jM}}QL9l~1OOfbZ1`>fL2g z4y9h5N>LS7N=YWVu{?m&^sTi6y$*!#dh419xRQ+c0eK>`jwIxe<1_Y~!ujM4&Dy9> z(d+<|D$Aqw5Px&3zz|{=(d8!(jasOG1iT6DOOxKzv&XcjLUiS|D;3sb3{a>V$2F-o znHA{B`a))kefZNIkX@qd4G?PT-yA{M%C9xQ0Jt=`*NGXza;f9;VM*p48!EpEWA!#d4TZ`GpI^bERG;oY>MrmoV;EBPj2u(JYG&!n7Z40^HDH zP2skFy{!bjYMzTnOG|6lYd%qkkcBf#?flj$E(!w_m9?&?N-gVkk(BPnIcM<9!sMtsm^V=1=!++WHk;up4 zeo+M9TzaiXvmlAB__N9Y&6oC78mLgQ{(+#FR{xQ~t(k9EiF>iFH|L=~`ho!}I@~ZS z5dFI(vA1@3-FE8GV0K#Wc2F~zW^2@$P+mtmBmG8!r#1ZKF6^O)a1!iv z)l1jO5X@ls-@g`Nf~}1s%U$2xJv|4Dzs=q2o3ql;6{OR$PBoaU$S|I&B&`pp_BvPw zdVSE@HsYm-`1s;wDd#kN#V6e}0ZmoyId1xF@klvC1Jl%(8Cz~@y(rs3^U|}~$&4pp zixz25!%@pQk0bN3w!5{dw4?c~0gRx3rs8tIIig2Vuq0DR<}uE+OL9+a*>(i0fZf68 zV;V=ddk@>aX-ltG=%;`EIhlu~K({_oK5@1w2AC?=TZ7+xes-pa)>fp#f|vo)SL}D_ zh*{~uO;V1h@VtiiIt{47hLPv2c5VcYk2{Mm175)oAAqyp9^U2Z;hbFP(vu^<6d^96&jDrT7KH*dLTEHJgqhA7L||8PB??SXE3Xk+L#rB!f=t9?k9-ar2uU z{i%z83si3l6YJpU*sd0hR*TJToqe4!MmlVEdYti{4uOscA=G$tLt<4wxIi0B`XH+=-n}ByVpd*jG2H!oY-UE<*|crAB{}F# zKPi+UeQK76$Wt#8fhJ0ECltK^C<9nT$(BD?cQDm%Z!VrJcPVTYd0NR{6`6q;=fnBs zWf$f88R_6i)$a;EN@;&yt}48ryopI0&(;Mz*6pA^^3PYw5Jmg?Tdj}O9&x`Gikh6p zBRl5SuC+>#AjL#|&v?Bo=hx{xOg)5r3ZmwJVzJ@>=6g0kC)Fw02AIJ~kIg}|rp~Hz zR|q{R(A>iw(fW)1U7>m3q#{rQA1s&CeUi_KgAcm~ONj1VR48f!PEILifJQA(2l4QC+|NHB0)rzpdnC?n#B8;Mj3%R& z0JSCN9?PS5KT>t4=+j{R%X0dEz&?aXEJM9SIV2I&kw<4j{pbo6coFr-aiD4j+ocYJsuB6EO2%oZ8BssVWG43&#QLKs^1z=aQgkus(#6zRHUzfwifdkXr7vj;P`?Ye?-)0L0GOo_>BXj>NPL ze}jmugp#$#@5OTKNu;)45aZ{~{&=YD{<}y>Wd_Bo=gzQlKWL6(30~`(7U1}OWmA)?(*;QNQ6$kYs#3l z;2Lz(eat-ITVUC{l+>%D&?~r2DM_McceO0>zH_B$PmMY`Ib~^$IfAUor9)8f zCt6r2xS}_^`Xn$()Ggl>;7r%jL7OM&qQ5bX=dW+pI}%CmL3>M7T}LOyHdMU6x8z9) zuUXVi40;ufSugnUhm-lPJ_G@IMun0uA3j_wqoMWYVnO4xoLbXah zkqc5tlh8d?1g%7K{n;BYNDm}kA=uZMY1znRgT+jJC|9+X4XSnB!nc3I^UJ-Y1=^Ud zkZf}Wv&NPwQDVRvGhF#WjIiH(mN*O59azc2o8maNqi6U+DLG9l%bXfrlRH)CW2T72 z^cx<>0Hy&pnX5#yWve;1N|xpeC~`~Uy438g8g#? zXoi{Lm1!I;1pnS`wJy>`$;ik6rm$I6xEPR?O=Wbo>JXO}(9pWrVpC^pjf#~W0Rz)B ze#)vZl`bQU2$qg#&sw0)B;KZrQ0}`p(6>acoJqumID789GGb!5a|!EZ@g-_+W0uCh zXF(n6Tu!Io`aPQ=e`qQDoMD8URi7H1=Ye$BR?9@x3%Dg{GhY3I^daVFeFFvh?ar<} z_WJ@}pk8{WYHPhV98qrdHc?$4ZJ?@GVZ|bGab$eA2Kfjmm9lcNh>7WlZqj^bU^;HB zb>F!KI}lXnJKjZR8wMitqjLRDCfu#-Gqu$>HxJZ}?Ce%UxE`)5{jiNVThs;&cPMEo zI+U8<%RPwr@+-&@I9*kXpA*(PXR3ytFL%jR$2J{F%wb(MwwV2%==KhB|1`<-_@gIf z_(E$`U%~>%wQu(;kvjVxU7QvP+Z%90kF60|CUUzWV?bv8O6Y#~%s`y9#u|C1-BKy| zUW^jC(8Hv9WZUw_`gPCh{@Q2>?!XwjY5$JUmxHAzljU|MDHmsiFuud8Hg7&>4jlZw z_Io?Om;Rb0sI4x+;qfy2{WtzJgGI+ zSR~){^3|xaqty52jcaOBu$l9v5E454ZC?)T6Mm=`XgvlCcXq^5ZqB@WMc){hJlHrA zIW{IdqXF!obqOdDUpqUeu3#HL^s@obdfZmu;Ym1+xx_0*3KQ2C`VDl3RcD!{0CiN= z3C`?5<(v!j1Dp_mT~Ax_2L8eKu+Pq+Y6`Qt`V@g6R;ShQ$vkG|ryHib0n3|!Yj?hz zh^4`2DhyqG=^H?g6{8M%KJg{Su&SzrqR);pCQt&tXB5^1d;_A#d1_tW-Og*nS+|3Z zDYP^+G(vk`p)0?f?dHn9Oudl-hDl0WL;25itu=q`HB46O9b8N`Kea4GD5fT-&Yu^J`z5mng9J%bU%cuRJMyFV0L|N2s`UiKRjo zS4Yeh;iFgv0Ld2If2jO8P0eyVJ0y}cCdZlNmp4;DZl?uYZoCy1O_pYG{x#z4K_5%y zyMdD)E`^VbLX36J1@p=pB@&y%2&PRFKF94Zo#2xbXvK_s+tWA zR@!Q#Yt27-egInk9%DMeD-~bW%vJ)3DcCe)g(TKr1^GKC3p>1L^64$=wb(mzQqVR{ zO=d%I^kt8U<_=YJayJYGB_(Mz9BpeuBKsDG1#T=46R^Z(ZzZwfoCkf0s;4T@B=dc5 zCq8R_I1w0Es1}#>_jb1OSqeEze>A(F;Dty7Wu$aX(1Ro6W=FQSw$0Hy=LFrUF%>

7mhU;*2@#-^!P#!cXS|Yt9|7hOh$4Mu@N@!IR}v7!c)8;yD@K&O6ELpM z&gHYX7u=6M93O!dL;;(>hc9=lF3K5Dq=mfFz+%JM`@_%VM9+i!d&60$h_a0`|3DV) zhyXm3vie_THYH7cUTk>pUoRJOn(^6*f`j%@a+3xzQtQzXfT4MpHr6L*5sf%5u*Q{+IXPdXie0_ox>Ph`6tzY z&y=qN>tj&6Mo%SdPGPBi3_U5ACh_U~v}ysF=*^WP=_dg?{NkUr*5h2Lsq7cx&zQ^Y z5aC6Qr<)fcb-7p`?|F)UYw?H?cmACxc-)k0(Db%f5|0dWpHm&632zDViSB}AI9qL4 zh65IC{28MW&96+c?lt+V6i=5J9Ni`(P+o(}KEGz6)5u`^RVc@2?wyTB^~axyg}0*H z(%Aag7E!R%=Ep>V15`w-osq^03_=n0rw4(O6kOJl*;^fqNbujl4lf1n8TJ?6_kq2S z$>oJOsQ(t0)xL6Q2G|0HBp|^LDqO>ZjR_zU^Bz4l?DnL>We#DU%>Fvo zeA)w?K0WIrsa?uPDN#ldVZ}mxD?&|a5$Gx_y!_PQGYO`tssB?yW6N_8&xF9>vO_5 zc^iNA~}fngxYkys<`3;=ji z?XdFDLw-41H!Z=Wm%&1vn@cdYM!(-2RZ6eH0eHxAT8(3L>Tct~cDq$Ogw^hBaeDf7 z>-HxN`}j$Reb2x+lf}B>?&QAhi~g1g^!S<*U0Fw;`6(s^iYw6mihv2Wwka}_#`qD2 zwY4ZqDlQ(*A;~CR;FuNm-BtM3JQ4|0KLmF#{0?BA-9b2_63E-a#*tZxvu!-#!FE-C zmlv7xiIIo1UIPnzbAFd8jJ`);L%qm(%V#Cp3GxVEd!0(5Qf#l18z1n)}ur`mn2fvvVDA z?5lZqM=8XH-TzA6^1wUiv!)s`>%W$6l-iwqa(@5YH)^bxiTzR5}95eMd$F7b2qy~kJ$j?1q=?O6L_qaP~L+RZf(OI@5DPgGb_1KLQ1fL(UpSC8wVn7W zD#BbXs)(_=&s4I=L@JDlRY}roSxl1nloAX~dr4FJ^G75xBkw~X?*q(j{!b5RXO`HF zGK|-RF_cugU-~|yoYnw-?e=|(=V1T(WHa+6wyZh>lfWJ`aQj+4JAO&R*wC3Oo`YpE z_5;-Dv&9JXXr3@6dGRE1B&Uei1M}jCnnx-yt>gvD0{C`Le+R$qt&g#{t${M%6xc<@ z0oN-aAo5IFdZNyQAF8k#YcZ1B%m5ZStPm)+S(upva&sT)xi8>GGKhC?FSJ$!Qhr#z z^c;I^LTTo1KBrdh#-&B0SxkQtFTZLwSPWVG?SPm~_c?VX zr_)LH+^5w>ZVejVhJZaAU>@U>(A@=|SwwdP!^?qXi^kSclq8-32$%?ll*P8Nky*DEtkA%;Y>-z2JHi=%=tWN?Ph(S`BAr%@|oB z1W>8MP7lXc%?45xfm10s_DrCilPj5>t@i@elfoCiiMT2th~p2uHL!{;hL|C(%5q(n zR-XK-0WQ6|-1GdaH?ed5I;&DGxevbo2>t+ebVl^K!3q%i`pS~@TZEPMT@3+$r1EN? z!;C6@CmlGQWnt&v&}bBHQ(mC?t?{n2sUc|HCIl4+M37QnhZu(}Eup>KGQRhg2zEq% zp|%PpLF1 zl`9}I0g!~zqJ9(ot@OHGNL6iNFr4My9FfEUDsJ}lJwopJ#lrMUA|5YbPWhuRb8RFC zTLb?&*ubtDJUiq2!`VJC;Qbd^;4+V`0!hWNs3_$hE-kICSe?<$4fMdK4=$3(t*=yK z2ZV)mf$8pL;J5_pMiYD1oWabQ;)j&l-n5Z-yns+#xo~gCs&}Hz*GBoxmZJCc(w(TK z+!U_|5tj~o6fcL*t~<~IX~p~=t!{6+`sX1!0OCt_LiY3co;?B%qZVFDpZGgiXT1o~ zo4e0-bf)L@(lSk-SRrRcdK?fNfUKZcbi;GnE|g_?kJFD2%Ev$LDnU=^&%u8BHL5qw-gQRa+-+ z?lP7qY;4_jG($r}{>Mirb1N$=@kXL@-yY^ES-HeAWcbmcGVk91d_z9`BY?pcUpCyj zA?h`8vZWs6oV{jEJW=P)o^Xwjk~ZOi@qrYTf>yxKU=PaJJ;%0BgY2$7q@=vHUvbLt z#425sJQHK?tzq{SBAYhMylyhxBtfjbGD?x6hd}g`JmZNq?jQk%ylK_7URRaJaMrHz z_WWU2%9OXPG9q6XA|NI%fnNek3J7SIEWwK8hoQA-vQyX3coHo2YP@;kvM#aRboKtP zfgK?)-;LYOw11C2h9V>N?ruz*UP138cEqugy*b=iDy5#h(2aN@e28JfwbzbDwi0go zksNiV3*c;r1$P3s@5TwyuW#EKR$^RqscQ;wU_uF@zmn(Xom1V#gMd<|P}L>9syz}}y2H;WGsrGG*ZoEw|4 zcRdOuR8jS<1+amI@EoejD2S>x=RR^Tv|4xdB=MJQDD#O$B|Ge5Sqx^8Kmh81v&5Q; zehLK(dSSmP+YVN0ZPq_^mZ9cb&dY5ygF8F51y{hcKmu{skJ9`9c-!aHaX1%MMdRkQ zzy1QGg;)K_!i-b4xxh;gyV#`b792B_A&ax!LdXrNb>u%!TynvfF_*QA) z(r54FG@^7NN_~0~8sS&eaZGs!a!K>*FYJ$3Ri6;FWa;NpiupJ9!*veEYFhs@m)&S4eY|I`ddt1pPVSle=_jZ01uonh)twhL_zv<* zuS#53@0EG^&M4ccakNcRF*$`Oh(te6ed<@JWqR(GdgPs~;S)|L?8s}@`<$pXL|O9u z6Bw5aeQON^#l?`;;qHK>As7rU_ozn$_oJ_GfA;T;sdHl$8@1hFQx_ayTv%8DofR?g z;zik(dFlRg4oMbv!c032XwKos-3P|PIyH`8sNWuh4RKxwkEPI{Zi|8>S4 z0-k9Ix^^h{{HdDEO**<>U_o}Blr+c3tE|HcJy{V43@Sx+p1yoT_$mi-Ydjkso4RcZA1mjAV2r<0n$^tV@I|T1~=5(QH4X#*I?s~e?eT_y+sq@cJ+XWuJW1NE7X1F0TU|E#&v~K+_AV?WP-al# zN)^f+@Vw)YgshY3e%duEDFQ5rfC+M!-ZJwi!-mM7LC`aTfBWNKzX&m1gQ8U)W9vES zsY^pr1Us^?YEnBrznbqtzRFuSMI|NOF+Z#eguRl3Crf`jeBH#c9Fbs8tNvTr{AJCT zZ}ZVwYUx()C~dGq_&JYz(!CE5WOuAbaP_)ZKl(2C`v)@U-@W|5d)@!%FaAIOfdA}u q{p+p!ue|pE{Y(G Date: Sun, 29 Oct 2023 16:23:03 +1000 Subject: [PATCH 67/95] Another tidy of CSharp_TestApp (Clipper2 DLL demo) --- DLL/CSharp_TestApp/Program.cs | 44 +++++++++++------------------------ 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/DLL/CSharp_TestApp/Program.cs b/DLL/CSharp_TestApp/Program.cs index 0c730308..5400fc77 100644 --- a/DLL/CSharp_TestApp/Program.cs +++ b/DLL/CSharp_TestApp/Program.cs @@ -21,68 +21,50 @@ public class Application static T[]? CreateCPath(T[] coords) { - int pathLen = coords.Count() / 2; + 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)); - int idx = 2; - for (int i = 0; i < pathLen; i++) - { - result[idx++] = coords[i * 2]; - result[idx++] = coords[i * 2 + 1]; - } + coords.CopyTo(result, 2); return result; } - static void AppendCPath(ref T[] cpaths, ref int idx, T[] cpath) - { - int pathLen = cpath.Count() / 2 - 1; - if (pathLen <= 0) return; - cpaths[idx++] = (T)Convert.ChangeType(pathLen, typeof(T)); - cpaths[idx++] = (T)Convert.ChangeType(0, typeof(T)); - for (int i = 1; i <= pathLen; i++) - { - cpaths[idx++] = cpath[i * 2]; - cpaths[idx++] = cpath[i * 2 + 1]; - } - } static T[] CreateCPaths(List listOfCPath) { int pathCount = listOfCPath.Count(); int arrayLen = 2; foreach (T[] path in listOfCPath) - arrayLen += path.Count(); + 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[] path in listOfCPath) - AppendCPath(ref result, ref idx, path); + foreach (T[] cpath in listOfCPath) + { + cpath.CopyTo(result, idx); + idx += cpath.Length; + } return result; } - // and to create cpaths that will contain just 1 path ... + // or create a cpaths array that contains just 1 path static T[] CreateCPaths(T[] coords) { - int pathLen = coords.Count() / 2, arrayLen = pathLen *2 + 2 + 2; + 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)); + 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)); - int idx = 4; - for (int i = 0; i < pathLen; i++) - { - result[idx++] = coords[i * 2]; - result[idx++] = coords[i * 2 + 1]; - } + coords.CopyTo(result, 4); return result; } From 855291096f32ffa1cedd41307af0716fc429acbf Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 29 Oct 2023 19:26:30 +1000 Subject: [PATCH 68/95] clipper.export.h code tidy --- .../include/clipper2/clipper.export.h | 173 ++++++------------ 1 file changed, 52 insertions(+), 121 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index bd51365d..22cb6203 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 October 2023 * +* Date : 29 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module exports the Clipper2 Library (ie DLL/so) * @@ -151,7 +151,7 @@ EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype, uint8_t fillrule, const CPaths64 subjects, const CPaths64 subjects_open, const CPaths64 clips, - int64_t*& sol_tree, CPaths64& solution_open, + CPolyTree64& sol_tree, CPaths64& solution_open, bool preserve_collinear = true, bool reverse_solution = false); EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, @@ -193,8 +193,8 @@ EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, ////////////////////////////////////////////////////// template -static void GetPathCountAndCPathsArrayLen(const std::vector < std::vector >>& paths, -size_t& cnt, size_t& array_len) +static void GetPathCountAndCPathsArrayLen(const Paths& paths, + size_t& cnt, size_t& array_len) { array_len = 2; cnt = 0; @@ -216,25 +216,27 @@ static size_t GetPolyPath64ArrayLen(const PolyPath64& pp) return result; } -static void GetPolytreeCountAndCStorageSize(const PolyTree64& tree, size_t& cnt, size_t& array_len) +static void GetPolytreeCountAndCStorageSize(const PolyTree64& tree, + size_t& cnt, size_t& array_len) { cnt = tree.Count(); // nb: top level count only array_len = GetPolyPath64ArrayLen(tree); } -static CPaths64 CreateCPaths64(const Paths64& paths) +template +static T* CreateCPaths(const Paths& paths) { size_t cnt, array_len; GetPathCountAndCPathsArrayLen(paths, cnt, array_len); - int64_t* result = new int64_t[array_len], * v = result; + T* result = new T[array_len], * v = result; *v++ = array_len; *v++ = cnt; - for (const Path64& path : paths) + for (const Path& path : paths) { if (!path.size()) continue; *v++ = path.size(); *v++ = 0; - for (const Point64& pt : path) + for (const Point& pt : path) { *v++ = pt.x; *v++ = pt.y; @@ -243,26 +245,6 @@ static CPaths64 CreateCPaths64(const Paths64& paths) return result; } -static CPathsD CreateCPathsD(const PathsD& paths) -{ - size_t cnt, array_len; - GetPathCountAndCPathsArrayLen(paths, cnt, array_len); - double* 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++ = 0; - for (const PointD& pt : path) - { - *v++ = pt.x; - *v++ = pt.y; - } - } - return result; -} CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale) { @@ -286,51 +268,30 @@ CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale) return result; } -static Paths64 ConvertCPaths64(const CPaths64 paths) +template +static Paths ConvertCPaths(T* paths) { - Paths64 result; + Paths result; if (!paths) return result; - int64_t* v = paths; ++v; + T* v = paths; ++v; size_t cnt = *v++; result.reserve(cnt); for (size_t i = 0; i < cnt; ++i) { size_t cnt2 = *v; v += 2; - Path64 path; + Path path; path.reserve(cnt2); for (size_t j = 0; j < cnt2; ++j) { - int64_t x = *v++, y = *v++; - path.push_back(Point64(x,y)); + T x = *v++, y = *v++; + path.push_back(Point(x, y)); } result.push_back(path); } return result; } -static PathsD ConvertCPathsD(const CPathsD paths) -{ - PathsD result; - if (!paths) return result; - double* v = paths; ++v; - size_t cnt = (size_t)*v++; - result.reserve(cnt); - for (size_t i = 0; i < cnt; ++i) - { - size_t cnt2 = (size_t)*v; - v += 2; - PathD path; - path.reserve(cnt2); - for (size_t j = 0; j < cnt2; ++j) - { - double x = *v++, y = *v++; - path.push_back(PointD(x, y)); - } - result.push_back(path); - } - return result; -} static Paths64 ConvertCPathsDToPaths64(const CPathsD paths, double scale) { @@ -357,66 +318,38 @@ static Paths64 ConvertCPathsDToPaths64(const CPathsD paths, double scale) return result; } -static void CreateCPolyPath64(const PolyPath64* pp, CPolyPath64& v) -{ - *v++ = pp->Polygon().size(); - *v++ = pp->Count(); - for (const Point64& pt : pp->Polygon()) - { - *v++ = pt.x; - *v++ = pt.y; - } - for (size_t i = 0; i < pp->Count(); ++i) - CreateCPolyPath64(pp->Child(i), v); -} - -static CPolyTree64 CreateCPolyTree64(const PolyTree64& tree) -{ - size_t cnt, array_len; - GetPolytreeCountAndCStorageSize(tree, cnt, array_len); - if (!cnt) return nullptr; - // allocate storage - int64_t* result = new int64_t[array_len]; - int64_t* v = &result[0]; - - *v++ = array_len; - *v++ = tree.Count(); - for (size_t i = 0; i < tree.Count(); ++i) - CreateCPolyPath64(tree.Child(i), v); - return result; -} - -static void CreateCPolyPathD(const PolyPath64* pp, CPolyPathD& v, double scale) +template +static void CreateCPolyPath(const PolyPath64* pp, T*& v, T scale) { - *v++ = (double)pp->Polygon().size(); - *v++ = (double)pp->Count(); + *v++ = (T)pp->Polygon().size(); + *v++ = (T)pp->Count(); for (const Point64& pt : pp->Polygon()) { - *v++ = pt.x * scale; - *v++ = pt.y * scale; + *v++ = (T)(pt.x * scale); + *v++ = (T)(pt.y * scale); } for (size_t i = 0; i < pp->Count(); ++i) - CreateCPolyPathD(pp->Child(i), v, scale); + CreateCPolyPath(pp->Child(i), v, scale); } - -static CPolyTreeD CreateCPolyTreeD(const PolyTree64& tree, double scale) +template +static T* CreateCPolyTree(const PolyTree64& tree, T scale) { + if (scale == 0) scale = 1; size_t cnt, array_len; GetPolytreeCountAndCStorageSize(tree, cnt, array_len); if (!cnt) return nullptr; // allocate storage - double* result = new double[array_len]; - double* v = result; + T* result = new T[array_len]; + T* v = result; - *v++ = (double)array_len; - *v++ = (double)tree.Count(); + *v++ = (T)array_len; + *v++ = (T)tree.Count(); for (size_t i = 0; i < tree.Count(); ++i) - CreateCPolyPathD(tree.Child(i), v, scale); + CreateCPolyPath(tree.Child(i), v, scale); return result; } - ////////////////////////////////////////////////////// // EXPORTED FUNCTION DEFINITIONS ////////////////////////////////////////////////////// @@ -436,9 +369,9 @@ 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 = ConvertCPaths64(subjects); - sub_open = ConvertCPaths64(subjects_open); - clp = ConvertCPaths64(clips); + sub = ConvertCPaths(subjects); + sub_open = ConvertCPaths(subjects_open); + clp = ConvertCPaths(clips); Clipper64 clipper; clipper.PreserveCollinear = preserve_collinear; @@ -448,23 +381,23 @@ EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, 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 = CreateCPaths64(sol); - solution_open = CreateCPaths64(sol_open); + solution = CreateCPaths(sol); + solution_open = CreateCPaths(sol_open); return 0; //success !! } EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype, uint8_t fillrule, const CPaths64 subjects, const CPaths64 subjects_open, const CPaths64 clips, - int64_t*& sol_tree, CPaths64& solution_open, + CPolyTree64& sol_tree, CPaths64& solution_open, bool preserve_collinear, bool reverse_solution) { if (cliptype > static_cast(ClipType::Xor)) return -4; if (fillrule > static_cast(FillRule::Negative)) return -3; Paths64 sub, sub_open, clp, sol_open; - sub = ConvertCPaths64(subjects); - sub_open = ConvertCPaths64(subjects_open); - clp = ConvertCPaths64(clips); + sub = ConvertCPaths(subjects); + sub_open = ConvertCPaths(subjects_open); + clp = ConvertCPaths(clips); PolyTree64 tree; Clipper64 clipper; @@ -476,8 +409,8 @@ EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype, if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open)) return -1; // clipping bug - should never happen :) - sol_tree = CreateCPolyTree64(tree); - solution_open = CreateCPaths64(sol_open); + sol_tree = CreateCPolyTree(tree, (int64_t)1); + solution_open = CreateCPaths(sol_open); return 0; //success !! } @@ -501,8 +434,7 @@ EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, clipper.PreserveCollinear = preserve_collinear; clipper.ReverseSolution = reverse_solution; if (sub.size() > 0) clipper.AddSubject(sub); - if (sub_open.size() > 0) - clipper.AddOpenSubject(sub_open); + 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; @@ -539,7 +471,7 @@ EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype, if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open)) return -1; // clipping bug - should never happen :) - solution = CreateCPolyTreeD(tree, 1/scale); + solution = CreateCPolyTree(tree, 1/scale); solution_open = CreateCPathsDFromPaths64(sol_open, 1 / scale); return 0; //success !! } @@ -549,14 +481,13 @@ EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths, double arc_tolerance, bool reverse_solution) { Paths64 pp; - pp = ConvertCPaths64(paths); - + pp = ConvertCPaths(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 CreateCPaths64(result); + return CreateCPaths(result); } EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, @@ -579,9 +510,9 @@ 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 = ConvertCPaths64(paths); + Paths64 pp = ConvertCPaths(paths); Paths64 result = rc.Execute(pp); - return CreateCPaths64(result); + return CreateCPaths(result); } EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int precision) @@ -604,9 +535,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 = ConvertCPaths64(paths); + Paths64 pp = ConvertCPaths(paths); Paths64 result = rcl.Execute(pp); - return CreateCPaths64(result); + return CreateCPaths(result); } EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, From 07be56242303d9da06e595a412ebeb33e1b532fc Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 29 Oct 2023 19:28:40 +1000 Subject: [PATCH 69/95] Forgot to update TestExportHeaders :) --- CPP/Tests/TestExportHeaders.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/CPP/Tests/TestExportHeaders.cpp b/CPP/Tests/TestExportHeaders.cpp index 01557700..e21a74dc 100644 --- a/CPP/Tests/TestExportHeaders.cpp +++ b/CPP/Tests/TestExportHeaders.cpp @@ -82,11 +82,11 @@ 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 = CreateCPaths64(subj); - CPaths64 c_clip = CreateCPaths64(clip); + CPaths64 c_subj = CreateCPaths(subj); + CPaths64 c_clip = CreateCPaths(clip); BooleanOp64(Intersection, EvenOdd, c_subj, c_subj_open, c_clip, c_sol, c_sol_open); - solution = ConvertCPaths64(c_sol); + solution = ConvertCPaths(c_sol); //clean up !!! delete[] c_subj; @@ -116,11 +116,11 @@ 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 = CreateCPathsD(subj); - CPathsD c_clip = CreateCPathsD(clip); + CPathsD c_subj = CreateCPaths(subj); + CPathsD c_clip = CreateCPaths(clip); BooleanOpD(Intersection, EvenOdd, c_subj, c_subj_open, c_clip, c_sol, c_sol_open); - solution = ConvertCPathsD(c_sol); + solution = ConvertCPaths(c_sol); //clean up !!! delete[] c_subj; @@ -147,8 +147,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 = CreateCPaths64(subj); - CPaths64 c_clip = CreateCPaths64(clip); + CPaths64 c_subj = CreateCPaths(subj); + CPaths64 c_clip = CreateCPaths(clip); int64_t* c_sol_tree = nullptr; BooleanOp_PolyTree64(Intersection, EvenOdd, c_subj, c_subj_open, c_clip, c_sol_tree, c_sol_open); @@ -192,8 +192,8 @@ 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 = CreateCPathsD(subj); - CPathsD c_clip = CreateCPathsD(clip); + CPathsD c_subj = CreateCPaths(subj); + CPathsD c_clip = CreateCPaths(clip); static const int precision = 4; From 01a3555e2009d839ed1ba603063d808adea18f59 Mon Sep 17 00:00:00 2001 From: angusj Date: Mon, 30 Oct 2023 10:28:12 +1000 Subject: [PATCH 70/95] Minor code tidy --- .../include/clipper2/clipper.core.h | 34 ++----------------- .../include/clipper2/clipper.export.h | 12 +++---- CPP/Examples/Inflate/Inflate.cpp | 13 +++---- 3 files changed, 16 insertions(+), 43 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index fe67a279..bcfbe4a1 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -19,7 +19,7 @@ #include #include #include -#include "clipper2/clipper.version.h" +#include "clipper.version.h" namespace Clipper2Lib { @@ -244,19 +244,13 @@ namespace Clipper2Lib T right; T bottom; - Rect() : - left(0), - top(0), - right(0), - bottom(0) {} - Rect(T l, T t, T r, T b) : left(l), top(t), right(r), bottom(b) {} - Rect(bool is_valid) + Rect(bool is_valid = true) { if (is_valid) { @@ -322,9 +316,7 @@ namespace Clipper2Lib } friend std::ostream& operator<<(std::ostream& os, const Rect& rect) { - os << "(" - << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom - << ")"; + os << "(" << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom << ") "; return os; } }; @@ -502,26 +494,6 @@ namespace Clipper2Lib return result; } - inline PathD Path64ToPathD(const Path64& path) - { - return TransformPath(path); - } - - inline PathsD Paths64ToPathsD(const Paths64& paths) - { - return TransformPaths(paths); - } - - inline Path64 PathDToPath64(const PathD& path) - { - return TransformPath(path); - } - - inline Paths64 PathsDToPaths64(const PathsD& paths) - { - return TransformPaths(paths); - } - template inline double Sqr(T val) { diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index 22cb6203..8c56c0d5 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -321,12 +321,12 @@ static Paths64 ConvertCPathsDToPaths64(const CPathsD paths, double scale) template static void CreateCPolyPath(const PolyPath64* pp, T*& v, T scale) { - *v++ = (T)pp->Polygon().size(); - *v++ = (T)pp->Count(); + *v++ = static_cast(pp->Polygon().size()); + *v++ = static_cast(pp->Count()); for (const Point64& pt : pp->Polygon()) { - *v++ = (T)(pt.x * scale); - *v++ = (T)(pt.y * scale); + *v++ = static_cast(pt.x * scale); + *v++ = static_cast(pt.y * scale); } for (size_t i = 0; i < pp->Count(); ++i) CreateCPolyPath(pp->Child(i), v, scale); @@ -343,8 +343,8 @@ static T* CreateCPolyTree(const PolyTree64& tree, T scale) T* result = new T[array_len]; T* v = result; - *v++ = (T)array_len; - *v++ = (T)tree.Count(); + *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); return result; diff --git a/CPP/Examples/Inflate/Inflate.cpp b/CPP/Examples/Inflate/Inflate.cpp index cc181169..298871e5 100644 --- a/CPP/Examples/Inflate/Inflate.cpp +++ b/CPP/Examples/Inflate/Inflate.cpp @@ -26,28 +26,29 @@ void DoSimpleShapes() FillRule fr2 = FillRule::EvenOdd; SvgWriter svg2; + op1.push_back(MakePath({ 80,60, 20,20, 180,20, 180,70, 25,150, 20,180, 180,180 })); op2 = InflatePaths(op1, 20, JoinType::Miter, EndType::Square, 3); SvgAddOpenSubject(svg2, op1, fr2, false); - SvgAddSolution(svg2, Paths64ToPathsD(op2), fr2, false); + SvgAddSolution(svg2, TransformPaths(op2), fr2, false); SvgAddCaption(svg2, "Miter Joins; Square Ends", 20, 210); op1 = TranslatePaths(op1, 210, 0); op2 = InflatePaths(op1, 20, JoinType::Square, EndType::Square); SvgAddOpenSubject(svg2, op1, fr2, false); - SvgAddSolution(svg2, Paths64ToPathsD(op2), fr2, false); + SvgAddSolution(svg2, TransformPaths(op2), fr2, false); SvgAddCaption(svg2, "Square Joins; Square Ends", 230, 210); op1 = TranslatePaths(op1, 210, 0); op2 = InflatePaths(op1, 20, JoinType::Bevel, EndType::Butt, 3); SvgAddOpenSubject(svg2, op1, fr2, false); - SvgAddSolution(svg2, Paths64ToPathsD(op2), fr2, false); + SvgAddSolution(svg2, TransformPaths(op2), fr2, false); SvgAddCaption(svg2, "Bevel Joins; Butt Ends", 440, 210); op1 = TranslatePaths(op1, 210, 0); op2 = InflatePaths(op1, 20, JoinType::Round, EndType::Round); SvgAddOpenSubject(svg2, op1, fr2, false); - SvgAddSolution(svg2, Paths64ToPathsD(op2), fr2, false); + SvgAddSolution(svg2, TransformPaths(op2), fr2, false); SvgAddCaption(svg2, "Round Joins; Round Ends", 650, 210); SvgSaveToFile(svg2, "open_paths.svg", 800, 600, 20); @@ -80,9 +81,9 @@ void DoSimpleShapes() co.Execute(20, p); pp.insert(pp.end(), p.begin(), p.end()); - FillRule fr = FillRule::EvenOdd; + FillRule fr3 = FillRule::EvenOdd; SvgWriter svg; - SvgAddSolution(svg, Paths64ToPathsD(pp), fr, false); + SvgAddSolution(svg, TransformPaths(pp), fr3, false); SvgSaveToFile(svg, "solution_off.svg", 800, 600, 20); System("solution_off.svg"); } From 8650a7b4557a2a6a0acf624a24a9ccc103ce8b45 Mon Sep 17 00:00:00 2001 From: Angus Johnson Date: Tue, 31 Oct 2023 14:42:25 +1000 Subject: [PATCH 71/95] Create ReadMe.md --- DLL/ReadMe.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 DLL/ReadMe.md diff --git a/DLL/ReadMe.md b/DLL/ReadMe.md new file mode 100644 index 00000000..f075f736 --- /dev/null +++ b/DLL/ReadMe.md @@ -0,0 +1,7 @@ +# Clipper2 DLL + +The Clipper2 library is written in several languages (C++, C# & Delphi Pascal) and can be compiled and linked directly into applications using any one of those languages. Now developers who use other programming languages can also access almost all of Clipper2's features using a C++ compiled dynamically linked library. And because the C++ code is significantly faster than either C# or Delphi, developers using those languages may also prefer using the DLL when performance is critical. + +This folder contains: +1. C++ code that compiles **Clipper2** into Windows DLLs (both 32bit and 64bit). +2. Several sample applications that demonstrate how to use these DLLs in non-C++ languages. From ff03eacb137c72cf9afc4109048f5b697b298026 Mon Sep 17 00:00:00 2001 From: Angus Johnson Date: Tue, 31 Oct 2023 16:15:00 +1000 Subject: [PATCH 72/95] Update README.md --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0e57a8b9..472daf25 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,14 @@ [![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) -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](http://www.angusj.com/clipper2/Docs/Changes.htm) in just about every way. ### Compilers - -C++: Requires C++17 (but could be modified to C++11 with only minor changes)
-C#: The core library uses Standard Library 2.0 but the sample code uses .NET6
-Delphi: Compiles with any version of Delphi back to Delphi 7. +| Lang. | Requirements | +| --- | --- | +| [**C++:**](https://github.com/AngusJohnson/Clipper2/tree/main/CPP) | Requires C++17 (could be modified to C++11 with relatively minor changes), **or**| +| [**C#:**](https://github.com/AngusJohnson/Clipper2/tree/main/CSharp) | The library uses Standard Library 2.0 but the sample code uses .NET6, **or**| +| [**Delphi:**](https://github.com/AngusJohnson/Clipper2/tree/main/Delphi) | Compiles with any version of Delphi from version 7 to current.| ### Documentation From 775ec626dfffadb2eac88ef9e91f7ae30ab35e28 Mon Sep 17 00:00:00 2001 From: angusj Date: Wed, 8 Nov 2023 21:28:48 +1000 Subject: [PATCH 73/95] Numerous minor bugfixes to ClipperOffset (#703) --- .../include/clipper2/clipper.core.h | 32 +- .../include/clipper2/clipper.engine.h | 2 +- .../include/clipper2/clipper.export.h | 15 +- CPP/Clipper2Lib/include/clipper2/clipper.h | 16 +- .../include/clipper2/clipper.minkowski.h | 4 +- .../include/clipper2/clipper.offset.h | 28 +- .../include/clipper2/clipper.rectclip.h | 4 +- CPP/Clipper2Lib/src/clipper.engine.cpp | 5 +- CPP/Clipper2Lib/src/clipper.offset.cpp | 366 ++++++++------- CPP/Tests/TestOffsetOrientation.cpp | 41 +- CPP/Tests/TestOffsets.cpp | 439 +++++++++--------- CPP/Utils/ClipFileSave.cpp | 14 +- CPP/Utils/ClipFileSave.h | 12 +- CPP/Utils/clipper.svg.cpp | 2 +- CSharp/Clipper2Lib/Clipper.Core.cs | 5 + CSharp/Clipper2Lib/Clipper.Offset.cs | 352 ++++++++------ Delphi/Clipper2Lib/Clipper.Core.pas | 24 +- Delphi/Clipper2Lib/Clipper.Offset.pas | 356 +++++++++----- Delphi/Clipper2Lib/Clipper.inc | 18 +- 19 files changed, 1001 insertions(+), 734 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index bcfbe4a1..0d9ec7b1 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 4 October 2023 * +* Date : 1 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library structures and functions * @@ -19,7 +19,7 @@ #include #include #include -#include "clipper.version.h" +#include "clipper2/clipper.version.h" namespace Clipper2Lib { @@ -229,6 +229,14 @@ namespace Clipper2Lib using Paths64 = std::vector< Path64>; using PathsD = std::vector< PathD>; + static const Point64 InvalidPoint64 = Point64( + (std::numeric_limits::max)(), + (std::numeric_limits::max)()); + static const PointD InvalidPointD = PointD( + (std::numeric_limits::max)(), + (std::numeric_limits::max)()); + + // Rect ------------------------------------------------------------------------ template @@ -259,10 +267,12 @@ namespace Clipper2Lib else { left = top = (std::numeric_limits::max)(); - right = bottom = -(std::numeric_limits::max)(); + right = bottom = (std::numeric_limits::lowest)(); } } + bool IsValid() const { return left != (std::numeric_limits::max)(); } + T Width() const { return right - left; } T Height() const { return bottom - top; } void Width(T width) { right = left + width; } @@ -344,10 +354,16 @@ namespace Clipper2Lib return result; } - static const Rect64 MaxInvalidRect64 = Rect64( - INT64_MAX, INT64_MAX, INT64_MIN, INT64_MIN); - static const RectD MaxInvalidRectD = RectD( - MAX_DBL, MAX_DBL, -MAX_DBL, -MAX_DBL); + static const Rect64 InvalidRect64 = Rect64( + (std::numeric_limits::max)(), + (std::numeric_limits::max)(), + (std::numeric_limits::lowest)(), + (std::numeric_limits::lowest)()); + static const RectD InvalidRectD = RectD( + (std::numeric_limits::max)(), + (std::numeric_limits::max)(), + (std::numeric_limits::lowest)(), + (std::numeric_limits::lowest)()); template Rect GetBounds(const Path& path) @@ -549,7 +565,7 @@ namespace Clipper2Lib inline void StripDuplicates( Path& path, bool is_closed_path) { //https://stackoverflow.com/questions/1041620/whats-the-most-efficient-way-to-erase-duplicates-and-sort-a-vector#:~:text=Let%27s%20compare%20three%20approaches%3A - path.erase(std::unique(path.begin(), path.end()),path.end()); + path.erase(std::unique(path.begin(), path.end()), path.end()); if (is_closed_path) while (path.size() > 1 && path.back() == path.front()) path.pop_back(); } diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h index e41fe633..fb95f69d 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h @@ -19,7 +19,7 @@ #include #include -#include "clipper.core.h" +#include "clipper2/clipper.core.h" namespace Clipper2Lib { diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index 8c56c0d5..8e0c7280 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 29 October 2023 * +* Date : 1 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module exports the Clipper2 Library (ie DLL/so) * @@ -138,7 +138,6 @@ EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD& p) delete[] p; } - // Boolean clipping: // cliptype: None=0, Intersection=1, Union=2, Difference=3, Xor=4 // fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3 @@ -226,7 +225,7 @@ static void GetPolytreeCountAndCStorageSize(const PolyTree64& tree, template static T* CreateCPaths(const Paths& paths) { - size_t cnt, array_len; + size_t cnt = 0, array_len = 0; GetPathCountAndCPathsArrayLen(paths, cnt, array_len); T* result = new T[array_len], * v = result; *v++ = array_len; @@ -496,13 +495,15 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, double arc_tolerance, bool reverse_solution) { if (precision < -8 || precision > 8 || !paths) return nullptr; + const double scale = std::pow(10, precision); ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution); Paths64 pp = ConvertCPathsDToPaths64(paths, scale); clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype)); Paths64 result; clip_offset.Execute(delta * scale, result); - return CreateCPathsDFromPaths64(result, 1/scale); + + return CreateCPathsDFromPaths64(result, 1 / scale); } EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths) @@ -526,7 +527,8 @@ EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int Paths64 pp = ConvertCPathsDToPaths64(paths, scale); class RectClip64 rc(rec); Paths64 result = rc.Execute(pp); - return CreateCPathsDFromPaths64(result, 1/scale); + + return CreateCPathsDFromPaths64(result, 1 / scale); } EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect, @@ -545,12 +547,13 @@ EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect, { if (CRectIsEmpty(rect) || !paths) return nullptr; if (precision < -8 || precision > 8) return nullptr; + const double scale = std::pow(10, precision); Rect64 r = ScaleRect(CRectToRect(rect), scale); class RectClipLines64 rcl(r); Paths64 pp = ConvertCPathsDToPaths64(paths, scale); Paths64 result = rcl.Execute(pp); - return CreateCPathsDFromPaths64(result, 1/scale); + return CreateCPathsDFromPaths64(result, 1 / scale); } } // end Clipper2Lib namespace diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.h b/CPP/Clipper2Lib/include/clipper2/clipper.h index b5ac6aa9..2481d1fb 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 16 July 2023 * +* Date : 1 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -14,11 +14,11 @@ #include #include -#include "clipper.core.h" -#include "clipper.engine.h" -#include "clipper.offset.h" -#include "clipper.minkowski.h" -#include "clipper.rectclip.h" +#include "clipper2/clipper.core.h" +#include "clipper2/clipper.engine.h" +#include "clipper2/clipper.offset.h" +#include "clipper2/clipper.minkowski.h" +#include "clipper2/clipper.rectclip.h" namespace Clipper2Lib { @@ -653,7 +653,7 @@ namespace Clipper2Lib { } template - inline Path SimplifyPath(const Path path, + inline Path SimplifyPath(const Path &path, double epsilon, bool isClosedPath = true) { const size_t len = path.size(), high = len -1; @@ -721,7 +721,7 @@ namespace Clipper2Lib { } template - inline Paths SimplifyPaths(const Paths paths, + inline Paths SimplifyPaths(const Paths &paths, double epsilon, bool isClosedPath = true) { Paths result; diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.minkowski.h b/CPP/Clipper2Lib/include/clipper2/clipper.minkowski.h index 71c221bb..ebddd08a 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.minkowski.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.minkowski.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 28 January 2023 * +* Date : 1 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Minkowski Sum and Difference * @@ -13,7 +13,7 @@ #include #include #include -#include "clipper.core.h" +#include "clipper2/clipper.core.h" namespace Clipper2Lib { diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.offset.h b/CPP/Clipper2Lib/include/clipper2/clipper.offset.h index e48ae9dc..a1b16b5e 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.offset.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.offset.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 24 September 2023 * +* Date : 5 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -34,13 +34,12 @@ class ClipperOffset { class Group { public: Paths64 paths_in; - Paths64 paths_out; - Path64 path; + std::vector bounds_list; + int lowest_path_idx = -1; bool is_reversed = false; JoinType join_type; EndType end_type; - Group(const Paths64& _paths, JoinType _join_type, EndType _end_type) : - paths_in(_paths), join_type(_join_type), end_type(_end_type) {} + Group(const Paths64& _paths, JoinType _join_type, EndType _end_type); }; int error_code_ = 0; @@ -51,6 +50,7 @@ class ClipperOffset { double step_sin_ = 0.0; double step_cos_ = 0.0; PathD norms; + Path64 path_out; Paths64 solution; std::vector groups_; JoinType join_type_ = JoinType::Bevel; @@ -66,15 +66,17 @@ class ClipperOffset { #endif DeltaCallback64 deltaCallback64_ = nullptr; - void DoBevel(Group& group, const Path64& path, size_t j, size_t k); - void DoSquare(Group& group, const Path64& path, size_t j, size_t k); - void DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a); - void DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle); + size_t CalcSolutionCapacity(); + bool CheckReverseOrientation(); + void DoBevel(const Path64& path, size_t j, size_t k); + void DoSquare(const Path64& path, size_t j, size_t k); + void DoMiter(const Path64& path, size_t j, size_t k, double cos_a); + void DoRound(const Path64& path, size_t j, size_t k, double angle); void BuildNormals(const Path64& path); - void OffsetPolygon(Group& group, Path64& path); - void OffsetOpenJoined(Group& group, Path64& path); - void OffsetOpenPath(Group& group, Path64& path); - void OffsetPoint(Group& group, Path64& path, size_t j, size_t k); + void OffsetPolygon(Group& group, const Path64& path); + void OffsetOpenJoined(Group& group, const Path64& path); + void OffsetOpenPath(Group& group, const Path64& path); + void OffsetPoint(Group& group, const Path64& path, size_t j, size_t k); void DoGroupOffset(Group &group); void ExecuteInternal(double delta); public: diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h b/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h index b6861c61..ff043f25 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.rectclip.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 30 May 2023 * +* Date : 1 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : FAST rectangular clipping * @@ -13,7 +13,7 @@ #include #include #include -#include "clipper.core.h" +#include "clipper2/clipper.core.h" namespace Clipper2Lib { diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index 0e1d8de9..14f978ed 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 19 October 2023 * +* Date : 1 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -15,6 +15,7 @@ #include #include "clipper2/clipper.engine.h" +#include "clipper2/clipper.h" // https://github.com/AngusJohnson/Clipper2/discussions/334 // #discussioncomment-4248602 @@ -2140,7 +2141,7 @@ namespace Clipper2Lib { horz_seg_list_.end(), [](HorzSegment& hs) { return UpdateHorzSegment(hs); }); if (j < 2) return; - std::sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter()); + std::stable_sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter()); HorzSegmentList::iterator hs1 = horz_seg_list_.begin(), hs2; HorzSegmentList::iterator hs_end = hs1 +j; diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index 0439ec52..9581a643 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 24 September 2023 * +* Date : 8 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -20,38 +20,59 @@ const double floating_point_tolerance = 1e-12; // Miscellaneous methods //------------------------------------------------------------------------------ -void GetBoundsAndLowestPolyIdx(const Paths64& paths, Rect64& r, int & idx) +void GetMultiBounds(const Paths64& paths, std::vector& recList, EndType end_type) { - idx = -1; - r = MaxInvalidRect64; - int64_t lpx = 0; - for (int i = 0; i < static_cast(paths.size()); ++i) - for (const Point64& p : paths[i]) + size_t min_path_len = (end_type == EndType::Polygon) ? 3 : 1; + recList.reserve(paths.size()); + for (const Path64& path : paths) + { + if (path.size() < min_path_len) { - if (p.y >= r.bottom) - { - if (p.y > r.bottom || p.x < lpx) - { - idx = i; - lpx = p.x; - r.bottom = p.y; - } - } - else if (p.y < r.top) r.top = p.y; - if (p.x > r.right) r.right = p.x; - else if (p.x < r.left) r.left = p.x; + recList.push_back(InvalidRect64); + continue; + } + int64_t x = path[0].x, y = path[0].y; + Rect64 r = Rect64(x, y, x, y); + for (const Point64& pt : path) + { + if (pt.y > r.bottom) r.bottom = pt.y; + else if (pt.y < r.top) r.top = pt.y; + if (pt.x > r.right) r.right = pt.x; + else if (pt.x < r.left) r.left = pt.x; } - //if (idx < 0) r = Rect64(0, 0, 0, 0); - //if (r.top == INT64_MIN) r.bottom = r.top; - //if (r.left == INT64_MIN) r.left = r.right; + recList.push_back(r); + } +} + +bool ValidateBounds(std::vector& recList, double delta) +{ + int64_t int_delta = static_cast(delta); + int64_t big = MAX_COORD - int_delta; + int64_t small = MIN_COORD + int_delta; + for (const Rect64& r : recList) + { + if (!r.IsValid()) continue; // ignore invalid paths + else if (r.left < small || r.right > big || + r.top < small || r.bottom > big) return false; + } + return true; } -bool IsSafeOffset(const Rect64& r, double abs_delta) +int GetLowestClosedPathIdx(std::vector& boundsList) { - return r.left > min_coord + abs_delta && - r.right < max_coord - abs_delta && - r.top > min_coord + abs_delta && - r.bottom < max_coord - abs_delta; + int i = -1, result = -1; + Point64 botPt = Point64(INT64_MAX, INT64_MIN); + for (const Rect64& r : boundsList) + { + ++i; + if (!r.IsValid()) continue; // ignore invalid paths + else if (r.bottom > botPt.y || (r.bottom == botPt.y && r.left < botPt.x)) + { + botPt = Point64(r.left, r.bottom); + result = static_cast(i); + } + } + return result; } PointD GetUnitNormal(const Point64& pt1, const Point64& pt2) @@ -125,6 +146,34 @@ inline void NegatePath(PathD& path) } } + +//------------------------------------------------------------------------------ +// ClipperOffset::Group methods +//------------------------------------------------------------------------------ + +ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType _end_type): + paths_in(_paths), join_type(_join_type), end_type(_end_type) +{ + bool is_joined = + (end_type == EndType::Polygon) || + (end_type == EndType::Joined); + for (Path64& p: paths_in) + StripDuplicates(p, is_joined); + + GetMultiBounds(paths_in, bounds_list, end_type); + if (end_type == EndType::Polygon) + { + lowest_path_idx = GetLowestClosedPathIdx(bounds_list); + is_reversed = (lowest_path_idx >= 0) && Area(paths_in[lowest_path_idx]) < 0; + } + else + { + lowest_path_idx = -1; + is_reversed = false; + } +} + + //------------------------------------------------------------------------------ // ClipperOffset methods //------------------------------------------------------------------------------ @@ -200,7 +249,7 @@ PointD IntersectPoint(const PointD& pt1a, const PointD& pt1b, } } -void ClipperOffset::DoBevel(Group& group, const Path64& path, size_t j, size_t k) +void ClipperOffset::DoBevel(const Path64& path, size_t j, size_t k) { PointD pt1, pt2; if (j == k) @@ -214,11 +263,11 @@ void ClipperOffset::DoBevel(Group& group, const Path64& path, size_t j, size_t k pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y); pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y); } - group.path.push_back(Point64(pt1)); - group.path.push_back(Point64(pt2)); + path_out.push_back(Point64(pt1)); + path_out.push_back(Point64(pt2)); } -void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t k) +void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k) { PointD vec; if (j == k) @@ -246,8 +295,8 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t pt.z = ptQ.z; #endif //get the second intersect point through reflecion - group.path.push_back(Point64(ReflectPoint(pt, ptQ))); - group.path.push_back(Point64(pt)); + path_out.push_back(Point64(ReflectPoint(pt, ptQ))); + path_out.push_back(Point64(pt)); } else { @@ -256,28 +305,28 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t #ifdef USINGZ pt.z = ptQ.z; #endif - group.path.push_back(Point64(pt)); + path_out.push_back(Point64(pt)); //get the second intersect point through reflecion - group.path.push_back(Point64(ReflectPoint(pt, ptQ))); + path_out.push_back(Point64(ReflectPoint(pt, ptQ))); } } -void ClipperOffset::DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a) +void ClipperOffset::DoMiter(const Path64& path, size_t j, size_t k, double cos_a) { double q = group_delta_ / (cos_a + 1); #ifdef USINGZ - group.path.push_back(Point64( + path_out.push_back(Point64( path[j].x + (norms[k].x + norms[j].x) * q, path[j].y + (norms[k].y + norms[j].y) * q, path[j].z)); #else - group.path.push_back(Point64( + path_out.push_back(Point64( path[j].x + (norms[k].x + norms[j].x) * q, path[j].y + (norms[k].y + norms[j].y) * q)); #endif } -void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle) +void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle) { if (deltaCallback64_) { // when deltaCallback64_ is assigned, group_delta_ won't be constant, @@ -298,9 +347,9 @@ void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k if (j == k) offsetVec.Negate(); #ifdef USINGZ - group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); + path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); #else - group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); + path_out.push_back(Point64(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 @@ -308,15 +357,15 @@ void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y, offsetVec.x * step_sin_ + offsetVec.y * step_cos_); #ifdef USINGZ - group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); + path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); #else - group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); + path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); #endif } - group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); + path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_)); } -void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) +void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size_t k) { // Let A = change in angle where edges join // A == 0: ie no change in angle (flat join) @@ -337,61 +386,51 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k) } if (std::fabs(group_delta_) <= floating_point_tolerance) { - group.path.push_back(path[j]); + path_out.push_back(path[j]); return; } if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) // test for concavity first (#593) { // is concave - group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_)); + path_out.push_back(GetPerpendic(path[j], norms[k], group_delta_)); // this extra point is the only (simple) way to ensure that // path reversals are fully cleaned with the trailing clipper - group.path.push_back(path[j]); // (#405) - group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); + path_out.push_back(path[j]); // (#405) + path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_)); } else if (cos_a > 0.999) // almost straight - less than 2.5 degree (#424, #526) { - DoMiter(group, path, j, k, cos_a); + DoMiter(path, j, k, cos_a); } else if (join_type_ == JoinType::Miter) { // miter unless the angle is so acute the miter would exceeds ML - if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a); - else DoSquare(group, path, j, k); + if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a); + else DoSquare(path, j, k); } else if (cos_a > 0.99 || join_type_ == JoinType::Bevel) // ie > 2.5 deg (see above) but less than ~8 deg ( acos(0.99) ) - DoBevel(group, path, j, k); + DoBevel(path, j, k); else if (join_type_ == JoinType::Round) - DoRound(group, path, j, k, std::atan2(sin_a, cos_a)); + DoRound(path, j, k, std::atan2(sin_a, cos_a)); else - DoSquare(group, path, j, k); + DoSquare(path, j, k); } -void ClipperOffset::OffsetPolygon(Group& group, Path64& path) +void ClipperOffset::OffsetPolygon(Group& group, const Path64& path) { - // when the path is contracting, make sure - // there is sufficient space to do so. //#593 - // nb: this will have a small impact on performance - double a = Area(path); - // contracting when orientation is opposite offset direction - if ((a < 0) != (group_delta_ < 0)) - { - Rect64 rec = GetBounds(path); - double offsetMinDim = std::fabs(group_delta_) * 2; - if (offsetMinDim > rec.Width() || offsetMinDim > rec.Height()) return; - } - + path_out.clear(); for (Path64::size_type j = 0, k = path.size() -1; j < path.size(); k = j, ++j) OffsetPoint(group, path, j, k); - group.paths_out.push_back(group.path); + solution.push_back(path_out); } -void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path) +void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path) { OffsetPolygon(group, path); - std::reverse(path.begin(), path.end()); + Path64 reverse_path(path); + std::reverse(reverse_path.begin(), reverse_path.end()); //rebuild normals // BuildNormals(path); std::reverse(norms.begin(), norms.end()); @@ -399,29 +438,28 @@ void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path) norms.erase(norms.begin()); NegatePath(norms); - group.path.clear(); - OffsetPolygon(group, path); + OffsetPolygon(group, reverse_path); } -void ClipperOffset::OffsetOpenPath(Group& group, Path64& path) +void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path) { // do the line start cap if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0); if (std::fabs(group_delta_) <= floating_point_tolerance) - group.path.push_back(path[0]); + path_out.push_back(path[0]); else { switch (end_type_) { case EndType::Butt: - DoBevel(group, path, 0, 0); + DoBevel(path, 0, 0); break; case EndType::Round: - DoRound(group, path, 0, 0, PI); + DoRound(path, 0, 0, PI); break; default: - DoSquare(group, path, 0, 0); + DoSquare(path, 0, 0); break; } } @@ -441,54 +479,41 @@ void ClipperOffset::OffsetOpenPath(Group& group, Path64& path) group_delta_ = deltaCallback64_(path, norms, highI, highI); if (std::fabs(group_delta_) <= floating_point_tolerance) - group.path.push_back(path[highI]); + path_out.push_back(path[highI]); else { switch (end_type_) { case EndType::Butt: - DoBevel(group, path, highI, highI); + DoBevel(path, highI, highI); break; case EndType::Round: - DoRound(group, path, highI, highI, PI); + DoRound(path, highI, highI, PI); break; default: - DoSquare(group, path, highI, highI); + DoSquare(path, highI, highI); break; } } for (size_t j = highI, k = 0; j > 0; k = j, --j) OffsetPoint(group, path, j, k); - group.paths_out.push_back(group.path); + solution.push_back(path_out); } void ClipperOffset::DoGroupOffset(Group& group) { - Rect64 r; - int idx = -1; - //the lowermost polygon must be an outer polygon. So we can use that as the - //designated orientation for outer polygons (needed for tidy-up clipping) - GetBoundsAndLowestPolyIdx(group.paths_in, r, idx); - if (idx < 0) return; - if (group.end_type == EndType::Polygon) { - double area = Area(group.paths_in[idx]); + if (group.lowest_path_idx < 0) return; //if (area == 0) return; // probably unhelpful (#430) - group.is_reversed = (area < 0); - if (group.is_reversed) group_delta_ = -delta_; - else group_delta_ = delta_; + group_delta_ = (group.is_reversed) ? -delta_ : delta_; } else - { - group.is_reversed = false; group_delta_ = std::abs(delta_) * 0.5; - } double abs_delta = std::fabs(group_delta_); - // do range checking - if (!IsSafeOffset(r, abs_delta)) + if (!ValidateBounds(group.bounds_list, abs_delta)) { DoError(range_error_i); error_code_ |= range_error_i; @@ -498,8 +523,7 @@ void ClipperOffset::DoGroupOffset(Group& group) join_type_ = group.join_type; end_type_ = group.end_type; - if (!deltaCallback64_ && - (group.join_type == JoinType::Round || group.end_type == EndType::Round)) + if (group.join_type == JoinType::Round || group.end_type == EndType::Round) { //calculate a sensible number of steps (for 360 deg for the given offset) // arcTol - when arc_tolerance_ is undefined (0), the amount of @@ -517,62 +541,77 @@ void ClipperOffset::DoGroupOffset(Group& group) steps_per_rad_ = steps_per_360 / (2 * PI); } - bool is_joined = - (end_type_ == EndType::Polygon) || - (end_type_ == EndType::Joined); - Paths64::iterator path_iter; - for(path_iter = group.paths_in.begin(); path_iter != group.paths_in.end(); ++path_iter) + size_t i = 0; + for (const Path64& path_in : group.paths_in) { - Path64 &path = *path_iter; - StripDuplicates(path, is_joined); - Path64::size_type cnt = path.size(); - if (cnt == 0 || ((cnt < 3) && group.end_type == EndType::Polygon)) - continue; + const Rect64& path_rect = group.bounds_list[i++]; + if (!path_rect.IsValid()) continue; + Path64::size_type pathLen = path_in.size(); + path_out.clear(); - group.path.clear(); - if (cnt == 1) // single point - only valid with open paths + if (pathLen == 1) // single point - only valid with open paths { if (group_delta_ < 1) continue; + const Point64& pt = path_in[0]; //single vertex so build a circle or square ... if (group.join_type == JoinType::Round) { double radius = abs_delta; int steps = static_cast(std::ceil(steps_per_rad_ * 2 * PI)); //#617 - group.path = Ellipse(path[0], radius, radius, steps); + path_out = Ellipse(pt, radius, radius, steps); #ifdef USINGZ - for (auto& p : group.path) p.z = path[0].z; + for (auto& p : path_out) p.z = pt.z; #endif } else { int d = (int)std::ceil(abs_delta); - r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d); - group.path = r.AsPath(); + Rect64 r = Rect64(pt.x - d, pt.y - d, pt.x + d, pt.y + d); + path_out = r.AsPath(); #ifdef USINGZ - for (auto& p : group.path) p.z = path[0].z; + for (auto& p : path_out) p.z = pt.z; #endif } - group.paths_out.push_back(group.path); - } - else - { - if ((cnt == 2) && (group.end_type == EndType::Joined)) - { - if (group.join_type == JoinType::Round) - end_type_ = EndType::Round; - else - end_type_ = EndType::Square; - } + solution.push_back(path_out); + continue; + } // end of offsetting a single (open path) point + + // when shrinking, then make sure the path can shrink that far (#593) + if (group_delta_ < 0 && + std::min(path_rect.Width(), path_rect.Height()) < -group_delta_ * 2) + continue; + + if ((pathLen == 2) && (group.end_type == EndType::Joined)) + end_type_ = (group.join_type == JoinType::Round) ? + EndType::Round : + EndType::Square; + + BuildNormals(path_in); + if (end_type_ == EndType::Polygon) OffsetPolygon(group, path_in); + else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, path_in); + else OffsetOpenPath(group, path_in); + } +} + + +size_t ClipperOffset::CalcSolutionCapacity() +{ + size_t result = 0; + for (const Group& g : groups_) + result += (g.end_type == EndType::Joined) ? g.paths_in.size() * 2 : g.paths_in.size(); + return result; +} - BuildNormals(path); - if (end_type_ == EndType::Polygon) OffsetPolygon(group, path); - else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, path); - else OffsetOpenPath(group, path); +bool ClipperOffset::CheckReverseOrientation() +{ + bool is_reversed_orientation = false; + for (const Group& g : groups_) + if (g.end_type == EndType::Polygon) + { + is_reversed_orientation = g.is_reversed; + break; } - } - solution.reserve(solution.size() + group.paths_out.size()); - copy(group.paths_out.begin(), group.paths_out.end(), back_inserter(solution)); - group.paths_out.clear(); + return is_reversed_orientation; } void ClipperOffset::ExecuteInternal(double delta) @@ -580,29 +619,29 @@ void ClipperOffset::ExecuteInternal(double delta) error_code_ = 0; solution.clear(); if (groups_.size() == 0) return; + solution.reserve(CalcSolutionCapacity()); - if (std::abs(delta) < 0.5) + if (std::abs(delta) < 0.5) // ie: offset is insignificant { + int64_t sol_size = 0; + for (const Group& group : groups_) sol_size += group.paths_in.size(); + solution.reserve(sol_size); for (const Group& group : groups_) - { - solution.reserve(solution.size() + group.paths_in.size()); copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(solution)); - } - } - else - { - temp_lim_ = (miter_limit_ <= 1) ? - 2.0 : - 2.0 / (miter_limit_ * miter_limit_); + return; + } - delta_ = delta; - std::vector::iterator git; - for (git = groups_.begin(); git != groups_.end(); ++git) - { - DoGroupOffset(*git); - if (!error_code_) continue; // all OK - solution.clear(); - } + temp_lim_ = (miter_limit_ <= 1) ? + 2.0 : + 2.0 / (miter_limit_ * miter_limit_); + + delta_ = delta; + std::vector::iterator git; + for (git = groups_.begin(); git != groups_.end(); ++git) + { + DoGroupOffset(*git); + if (!error_code_) continue; // all OK + solution.clear(); } } @@ -613,19 +652,17 @@ void ClipperOffset::Execute(double delta, Paths64& paths) ExecuteInternal(delta); if (!solution.size()) return; - paths = solution; + bool paths_reversed = CheckReverseOrientation(); //clean up self-intersections ... Clipper64 c; c.PreserveCollinear = false; //the solution should retain the orientation of the input - c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed; + c.ReverseSolution = (reverse_solution_ != paths_reversed); #ifdef USINGZ - if (zCallback64_) { - c.SetZCallback(zCallback64_); - } + if (zCallback64_) { c.SetZCallback(zCallback64_); } #endif c.AddSubject(solution); - if (groups_[0].is_reversed) + if (paths_reversed) c.Execute(ClipType::Union, FillRule::Negative, paths); else c.Execute(ClipType::Union, FillRule::Positive, paths); @@ -639,18 +676,21 @@ void ClipperOffset::Execute(double delta, PolyTree64& polytree) ExecuteInternal(delta); if (!solution.size()) return; + bool paths_reversed = CheckReverseOrientation(); //clean up self-intersections ... Clipper64 c; c.PreserveCollinear = false; //the solution should retain the orientation of the input - c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed; + c.ReverseSolution = reverse_solution_ != paths_reversed; #ifdef USINGZ if (zCallback64_) { c.SetZCallback(zCallback64_); } #endif c.AddSubject(solution); - if (groups_[0].is_reversed) + + + if (paths_reversed) c.Execute(ClipType::Union, FillRule::Negative, polytree); else c.Execute(ClipType::Union, FillRule::Positive, polytree); diff --git a/CPP/Tests/TestOffsetOrientation.cpp b/CPP/Tests/TestOffsetOrientation.cpp index 03c1f11a..8ebfc533 100644 --- a/CPP/Tests/TestOffsetOrientation.cpp +++ b/CPP/Tests/TestOffsetOrientation.cpp @@ -1,21 +1,32 @@ #include #include "clipper2/clipper.offset.h" +#include "ClipFileLoad.h" -TEST(Clipper2Tests, TestOffsettingOrientation) { - Clipper2Lib::ClipperOffset co; +TEST(Clipper2Tests, TestOffsettingOrientation1) { + const Clipper2Lib::Paths64 subject = { Clipper2Lib::MakePath({ 0,0, 0,5, 5,5, 5,0 }) }; + Clipper2Lib::Paths64 solution = Clipper2Lib::InflatePaths(subject, 1, + Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); + ASSERT_EQ(solution.size(), 1); + //when offsetting, output orientation should match input + EXPECT_TRUE(Clipper2Lib::IsPositive(subject[0]) == Clipper2Lib::IsPositive(solution[0])); +} - const Clipper2Lib::Path64 input = { - Clipper2Lib::Point64(0, 0), - Clipper2Lib::Point64(0, 5), - Clipper2Lib::Point64(5, 5), - Clipper2Lib::Point64(5, 0) - }; +TEST(Clipper2Tests, TestOffsettingOrientation2) { - co.AddPath(input, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); - Clipper2Lib::Paths64 outputs; - co.Execute(1, outputs); + const Clipper2Lib::Paths64 subject = { + Clipper2Lib::MakePath({20, 220, 280, 220, 280, 280, 20, 280}), + Clipper2Lib::MakePath({0, 200, 0, 300, 300, 300, 300, 200}) + }; + Clipper2Lib::ClipperOffset co; + co.ReverseSolution(true); // could also assign using a parameter in ClipperOffset's constructor + co.AddPaths(subject, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); + Clipper2Lib::Paths64 solution; + co.Execute(5, solution); + + ASSERT_EQ(solution.size(), 2); + // when offsetting, output orientation should match input EXCEPT when ReverseSolution == true + // HOWEVER, input path order MANY NOT MATCH output path order, for example when inner paths (holes) + // are defined before their container outer paths (as above). + EXPECT_TRUE(Clipper2Lib::IsPositive(subject[1]) != Clipper2Lib::IsPositive(solution[0])); +} - ASSERT_EQ(outputs.size(), 1); - //when offsetting, output orientation should match input - EXPECT_TRUE(Clipper2Lib::IsPositive(input) == Clipper2Lib::IsPositive(outputs[0])); -} \ No newline at end of file diff --git a/CPP/Tests/TestOffsets.cpp b/CPP/Tests/TestOffsets.cpp index 6fa5e9db..84776ad9 100644 --- a/CPP/Tests/TestOffsets.cpp +++ b/CPP/Tests/TestOffsets.cpp @@ -151,225 +151,226 @@ TEST(Clipper2Tests, TestOffsets4) // see #482 TEST(Clipper2Tests, TestOffsets5) // modified from #593 (tests offset clean up) { Paths64 subject = { - {{524,1483}, {524,2711}, {610,2744}, {693,2782}, {773,2825}, {852,2872}, - {927,2924}, {999,2980}, {1068,3040}, {1133,3103}, {1195,3171}, {1252,3242}, - {1305,3316}, {1354,3394}, {1398,3473}, {1437,3556}, {1472,3640}, {1502,3727}, - {1526,3815}, {1546,3904}, {1560,3994}, {1569,4085}, {1573,4176}, {1571,4267}, - {1564,4358}, {1552,4449}, {1535,4539}, {1512,4627}, {1485,4714}, {1452,4799}, - {1414,4883}, {1372,4964}, {1325,5042}, {1274,5117}, {1218,5190}, {1158,5259}, - {1094,5324}, {1027,5386}, {956,5443}, {882,5497}, {805,5546}, {725,5590}, - {643,5630}, {559,5665}, {524,5677}, {524,6906}, {610,6939}, {693,6977}, - {773,7019}, {852,7066}, {927,7118}, {999,7174}, {1068,7234}, {1133,7298}, - {1195,7365}, {1252,7436}, {1305,7511}, {1354,7588}, {1398,7668}, {1437,7750}, - {1472,7835}, {1502,7921}, {1526,8009}, {1546,8098}, {1560,8188}, {1569,8279}, - {1573,8370}, {1571,8462}, {1564,8553}, {1552,8643}, {1535,8733}, {1512,8821}, - {1485,8908}, {1452,8994}, {1414,9077}, {1372,9158}, {1325,9236}, {1274,9312}, - {1218,9384}, {1158,9453}, {1094,9518}, {1027,9580}, {956,9638}, {882,9691}, - {805,9740}, {725,9784}, {643,9824}, {559,9859}, {524,9872}, {524,11100}, - {610,11133}, {693,11171}, {773,11213}, {852,11261}, {927,11312}, {999,11368}, - {1068,11428}, {1133,11492}, {1195,11560}, {1252,11631}, {1305,11705}, {1354,11782}, - {1398,11862}, {1437,11945}, {1472,12029}, {1502,12115}, {1526,12203}, {1546,12293}, - {1560,12383}, {1569,12474}, {1573,12565}, {1571,12656}, {1564,12747}, {1552,12838}, - {1535,12927}, {1512,13016}, {1485,13103}, {1452,13188}, {1414,13271}, {1372,13352}, - {1325,13431}, {1274,13506}, {1218,13578}, {1158,13647}, {1094,13713}, {1027,13774}, - {956,13832}, {882,13885}, {805,13934}, {725,13979}, {643,14019}, {559,14053}, - {524,14066}, {524,15295}, {610,15327}, {693,15365}, {773,15408}, {852,15455}, - {927,15507}, {999,15563}, {1068,15623}, {1133,15687}, {1195,15754}, {1252,15825}, - {1305,15899}, {1354,15977}, {1398,16057}, {1437,16139}, {1472,16223}, {1502,16310}, - {1526,16398}, {1546,16487}, {1560,16577}, {1569,16668}, {1573,16759}, {1571,16850}, - {1564,16942}, {1552,17032}, {1535,17122}, {1512,17210}, {1485,17297}, {1452,17382}, - {1414,17466}, {1372,17547}, {1325,17625}, {1274,17700}, {1218,17773}, {1158,17842}, - {1094,17907}, {1027,17969}, {956,18026}, {882,18080}, {805,18129}, {725,18173}, - {643,18213}, {559,18248}, {524,18260}, {524,19489}, {610,19522}, {693,19560}, - {773,19602}, {852,19649}, {927,19701}, {999,19757}, {1068,19817}, {1133,19881}, - {1195,19948}, {1252,20019}, {1305,20094}, {1354,20171}, {1398,20251}, {1437,20333}, - {1472,20418}, {1502,20504}, {1526,20592}, {1546,20681}, {1560,20771}, {1569,20862}, - {1573,20954}, {1571,21045}, {1564,21136}, {1552,21226}, {1535,21316}, {1512,21404}, - {1485,21492}, {1452,21577}, {1414,21660}, {1372,21741}, {1325,21819}, {1274,21895}, - {1218,21967}, {1158,22036}, {1094,22101}, {1027,22163}, {956,22221}, {882,22274}, - {805,22323}, {725,22368}, {643,22407}, {559,22442}, {524,22455}, {524,23683}, - {610,23716}, {693,23754}, {773,23797}, {852,23844}, {927,23895}, {999,23951}, - {1068,24011}, {1133,24075}, {1195,24143}, {1252,24214}, {1305,24288}, {1354,24365}, - {1398,24445}, {1437,24528}, {1472,24612}, {1502,24698}, {1526,24786}, {1546,24876}, - {1560,24966}, {1569,25057}, {1573,25148}, {1571,25239}, {1564,25330}, {1552,25421}, - {1535,25510}, {1512,25599}, {1485,25686}, {1452,25771}, {1414,25854}, {1372,25935}, - {1325,26014}, {1274,26089}, {1218,26161}, {1158,26230}, {1094,26296}, {1027,26357}, - {956,26415}, {882,26468}, {805,26517}, {725,26562}, {643,26602}, {559,26636}, - {524,26649}, {524,27878}, {610,27910}, {693,27948}, {773,27991}, {852,28038}, - {927,28090}, {999,28146}, {1068,28206}, {1133,28270}, {1195,28337}, {1252,28408}, - {1305,28482}, {1354,28560}, {1398,28640}, {1437,28722}, {1472,28806}, {1502,28893}, - {1526,28981}, {1546,29070}, {1560,29160}, {1569,29251}, {1573,29342}, {1571,29434}, - {1564,29525}, {1552,29615}, {1535,29705}, {1512,29793}, {1485,29880}, {1452,29965}, - {1414,30049}, {1372,30130}, {1325,30208}, {1274,30283}, {1218,30356}, {1158,30425}, - {1094,30490}, {1027,30552}, {956,30609}, {882,30663}, {805,30712}, {725,30756}, - {643,30796}, {559,30831}, {524,30843}, {524,32072}, {609,32105}, {692,32142}, - {773,32185}, {851,32232}, {926,32283}, {998,32339}, {1066,32398}, {1131,32462}, - {1193,32529}, {1250,32600}, {1303,32674}, {1352,32751}, {1396,32830}, {1436,32912}, - {1470,32996}, {1483,33031}, {3131,33031}, {3164,32945}, {3202,32862}, {3244,32781}, - {3291,32703}, {3343,32628}, {3399,32556}, {3459,32487}, {3523,32422}, {3591,32360}, - {3662,32303}, {3736,32250}, {3813,32201}, {3893,32157}, {3975,32117}, {4060,32083}, - {4146,32053}, {4234,32028}, {4323,32009}, {4413,31995}, {4504,31986}, {4596,31982}, - {4687,31984}, {4778,31991}, {4868,32003}, {4958,32020}, {5047,32043}, {5134,32070}, - {5219,32103}, {5302,32141}, {5383,32183}, {5461,32230}, {5537,32281}, {5609,32337}, - {5678,32397}, {5744,32460}, {5805,32528}, {5863,32599}, {5916,32673}, {5965,32750}, - {6010,32830}, {6049,32912}, {6084,32996}, {6097,33031}, {7745,33031}, {7778,32945}, - {7815,32862}, {7858,32781}, {7905,32703}, {7957,32628}, {8013,32556}, {8073,32487}, - {8137,32422}, {8204,32360}, {8275,32303}, {8350,32250}, {8427,32201}, {8507,32157}, - {8589,32117}, {8674,32083}, {8760,32053}, {8848,32028}, {8937,32009}, {9027,31995}, - {9118,31986}, {9209,31982}, {9301,31984}, {9392,31991}, {9482,32003}, {9572,32020}, - {9660,32043}, {9747,32070}, {9833,32103}, {9916,32141}, {9997,32183}, {10075,32230}, - {10151,32281}, {10223,32337}, {10292,32397}, {10357,32460}, {10419,32528}, {10477,32599}, - {10530,32673}, {10579,32750}, {10623,32830}, {10663,32912}, {10698,32996}, {10711,33031}, - {12358,33031}, {12391,32945}, {12429,32862}, {12472,32781}, {12519,32703}, {12571,32628}, - {12627,32556}, {12687,32487}, {12750,32422}, {12818,32360}, {12889,32303}, {12963,32250}, - {13041,32201}, {13120,32157}, {13203,32117}, {13287,32083}, {13374,32053}, {13462,32028}, - {13551,32009}, {13641,31995}, {13732,31986}, {13823,31982}, {13914,31984}, {14005,31991}, - {14096,32003}, {14186,32020}, {14274,32043}, {14361,32070}, {14446,32103}, {14530,32141}, - {14611,32183}, {14689,32230}, {14764,32281}, {14837,32337}, {14906,32397}, {14971,32460}, - {15033,32528}, {15090,32599}, {15144,32673}, {15193,32750}, {15237,32830}, {15277,32912}, - {15312,32996}, {15324,33031}, {16972,33031}, {17005,32945}, {17043,32862}, {17086,32781}, - {17133,32703}, {17184,32628}, {17240,32556}, {17300,32487}, {17364,32422}, {17432,32360}, - {17503,32303}, {17577,32250}, {17654,32201}, {17734,32157}, {17817,32117}, {17901,32083}, - {17987,32053}, {18075,32028}, {18165,32009}, {18255,31995}, {18346,31986}, {18437,31982}, - {18528,31984}, {18619,31991}, {18710,32003}, {18799,32020}, {18888,32043}, {18975,32070}, - {19060,32103}, {19143,32141}, {19224,32183}, {19303,32230}, {19378,32281}, {19450,32337}, - {19519,32397}, {19585,32460}, {19646,32528}, {19704,32599}, {19757,32673}, {19806,32750}, - {19851,32830}, {19891,32912}, {19926,32996}, {19938,33031}, {21586,33031}, {21619,32945}, - {21657,32862}, {21699,32781}, {21747,32703}, {21798,32628}, {21854,32556}, {21914,32487}, - {21978,32422}, {22046,32360}, {22117,32303}, {22191,32250}, {22268,32201}, {22348,32157}, - {22430,32117}, {22515,32083}, {22601,32053}, {22689,32028}, {22778,32009}, {22869,31995}, - {22959,31986}, {23051,31982}, {23142,31984}, {23233,31991}, {23324,32003}, {23413,32020}, - {23502,32043}, {23589,32070}, {23674,32103}, {23757,32141}, {23838,32183}, {23916,32230}, - {23992,32281}, {24064,32337}, {24133,32397}, {24199,32460}, {24260,32528}, {24318,32599}, - {24371,32673}, {24420,32750}, {24465,32830}, {24504,32912}, {24539,32996}, {24552,33031}, - {26200,33031}, {26233,32945}, {26271,32862}, {26313,32781}, {26360,32703}, {26412,32628}, - {26468,32556}, {26528,32487}, {26592,32422}, {26659,32360}, {26730,32303}, {26805,32250}, - {26882,32201}, {26962,32157}, {27044,32117}, {27129,32083}, {27215,32053}, {27303,32028}, - {27392,32009}, {27482,31995}, {27573,31986}, {27664,31982}, {27756,31984}, {27847,31991}, - {27937,32003}, {28027,32020}, {28115,32043}, {28202,32070}, {28288,32103}, {28371,32141}, - {28452,32183}, {28530,32230}, {28606,32281}, {28678,32337}, {28747,32397}, {28812,32460}, - {28874,32528}, {28932,32599}, {28985,32673}, {29034,32750}, {29078,32830}, {29118,32912}, - {29153,32996}, {29166,33031}, {30814,33031}, {30847,32945}, {30884,32862}, {30927,32781}, - {30974,32703}, {31026,32628}, {31082,32556}, {31142,32487}, {31206,32422}, {31273,32360}, - {31344,32303}, {31418,32250}, {31496,32201}, {31576,32157}, {31658,32117}, {31742,32083}, - {31829,32053}, {31917,32028}, {32006,32009}, {32096,31995}, {32187,31986}, {32278,31982}, - {32370,31984}, {32461,31991}, {32551,32003}, {32641,32020}, {32729,32043}, {32816,32070}, - {32902,32103}, {32985,32141}, {33066,32183}, {33144,32230}, {33219,32281}, {33292,32337}, - {33361,32397}, {33426,32460}, {33488,32528}, {33545,32599}, {33599,32673}, {33648,32750}, - {33692,32830}, {33732,32912}, {33767,32996}, {33779,33031}, {35427,33031}, {35460,32946}, - {35498,32863}, {35540,32782}, {35587,32704}, {35639,32629}, {35694,32557}, {35754,32489}, - {35818,32423}, {35885,32362}, {35956,32305}, {36029,32252}, {36106,32203}, {36186,32159}, - {36268,32119}, {36352,32084}, {36386,32072}, {36386,30843}, {36301,30810}, {36218,30773}, - {36137,30730}, {36059,30683}, {35983,30631}, {35911,30575}, {35842,30515}, {35777,30451}, - {35716,30384}, {35658,30313}, {35605,30239}, {35557,30161}, {35512,30081}, {35473,29999}, - {35438,29915}, {35409,29828}, {35384,29740}, {35364,29651}, {35350,29561}, {35341,29470}, - {35338,29379}, {35339,29287}, {35346,29196}, {35358,29106}, {35376,29016}, {35398,28928}, - {35426,28841}, {35458,28755}, {35496,28672}, {35538,28591}, {35585,28513}, {35637,28438}, - {35692,28365}, {35752,28296}, {35816,28231}, {35883,28169}, {35954,28112}, {36028,28058}, - {36105,28009}, {36185,27965}, {36267,27925}, {36352,27890}, {36386,27878}, {36386,26649}, - {36301,26616}, {36218,26578}, {36137,26536}, {36059,26489}, {35983,26437}, {35911,26381}, - {35842,26321}, {35777,26257}, {35716,26189}, {35658,26118}, {35605,26044}, {35557,25967}, - {35512,25887}, {35473,25805}, {35438,25720}, {35409,25634}, {35384,25546}, {35364,25457}, - {35350,25366}, {35341,25276}, {35338,25184}, {35339,25093}, {35346,25002}, {35358,24912}, - {35376,24822}, {35398,24733}, {35426,24646}, {35458,24561}, {35496,24478}, {35538,24397}, - {35585,24319}, {35637,24243}, {35692,24171}, {35752,24102}, {35816,24036}, {35883,23975}, - {35954,23917}, {36028,23864}, {36105,23815}, {36185,23770}, {36267,23731}, {36352,23696}, - {36386,23683}, {36386,22455}, {36301,22422}, {36218,22384}, {36137,22341}, {36059,22294}, - {35983,22243}, {35911,22187}, {35842,22127}, {35777,22063}, {35716,21995}, {35658,21924}, - {35605,21850}, {35557,21773}, {35512,21693}, {35473,21610}, {35438,21526}, {35409,21440}, - {35384,21352}, {35364,21262}, {35350,21172}, {35341,21081}, {35338,20990}, {35339,20899}, - {35346,20808}, {35358,20717}, {35376,20628}, {35398,20539}, {35426,20452}, {35458,20367}, - {35496,20284}, {35538,20203}, {35585,20124}, {35637,20049}, {35692,19976}, {35752,19907}, - {35816,19842}, {35883,19780}, {35954,19723}, {36028,19669}, {36105,19620}, {36185,19576}, - {36267,19536}, {36352,19501}, {36386,19489}, {36386,18260}, {36301,18227}, {36218,18190}, - {36137,18147}, {36059,18100}, {35983,18048}, {35911,17992}, {35842,17932}, {35777,17868}, - {35716,17801}, {35658,17730}, {35605,17655}, {35557,17578}, {35512,17498}, {35473,17416}, - {35438,17332}, {35409,17245}, {35384,17157}, {35364,17068}, {35350,16978}, {35341,16887}, - {35338,16796}, {35339,16704}, {35346,16613}, {35358,16523}, {35376,16433}, {35398,16345}, - {35426,16258}, {35458,16172}, {35496,16089}, {35538,16008}, {35585,15930}, {35637,15854}, - {35692,15782}, {35752,15713}, {35816,15648}, {35883,15586}, {35954,15529}, {36028,15475}, - {36105,15426}, {36185,15382}, {36267,15342}, {36352,15307}, {36386,15295}, {36386,14066}, - {36301,14033}, {36218,13995}, {36137,13953}, {36059,13906}, {35983,13854}, {35911,13798}, - {35842,13738}, {35777,13674}, {35716,13606}, {35658,13535}, {35605,13461}, {35557,13384}, - {35512,13304}, {35473,13222}, {35438,13137}, {35409,13051}, {35384,12963}, {35364,12874}, - {35350,12783}, {35341,12693}, {35338,12601}, {35339,12510}, {35346,12419}, {35358,12328}, - {35376,12239}, {35398,12150}, {35426,12063}, {35458,11978}, {35496,11895}, {35538,11814}, - {35585,11736}, {35637,11660}, {35692,11588}, {35752,11519}, {35816,11453}, {35883,11392}, - {35954,11334}, {36028,11281}, {36105,11232}, {36185,11187}, {36267,11148}, {36352,11113}, - {36386,11100}, {36386,9872}, {36301,9839}, {36218,9801}, {36137,9758}, {36059,9711}, - {35983,9660}, {35911,9604}, {35842,9544}, {35777,9480}, {35716,9412}, {35658,9341}, - {35605,9267}, {35557,9190}, {35512,9110}, {35473,9027}, {35438,8943}, {35409,8856}, - {35384,8769}, {35364,8679}, {35350,8589}, {35341,8498}, {35338,8407}, {35339,8316}, - {35346,8225}, {35358,8134}, {35376,8045}, {35398,7956}, {35426,7869}, {35458,7784}, - {35496,7700}, {35538,7620}, {35585,7541}, {35637,7466}, {35692,7393}, {35752,7324}, - {35816,7259}, {35883,7197}, {35954,7140}, {36028,7086}, {36105,7037}, {36185,6993}, - {36267,6953}, {36352,6918}, {36386,6906}, {36386,5677}, {36301,5644}, {36218,5607}, - {36137,5564}, {36059,5517}, {35983,5465}, {35911,5409}, {35842,5349}, {35777,5285}, - {35716,5218}, {35658,5147}, {35605,5072}, {35557,4995}, {35512,4915}, {35473,4833}, - {35438,4748}, {35409,4662}, {35384,4574}, {35364,4485}, {35350,4395}, {35341,4304}, - {35338,4213}, {35339,4121}, {35346,4030}, {35358,3940}, {35376,3850}, {35398,3762}, - {35426,3675}, {35458,3589}, {35496,3506}, {35538,3425}, {35585,3347}, {35637,3271}, - {35692,3199}, {35752,3130}, {35816,3065}, {35883,3003}, {35954,2945}, {36028,2892}, - {36105,2843}, {36185,2799}, {36267,2759}, {36352,2724}, {36386,2711}, {36386,1483}, - {36301,1450}, {36218,1413}, {36138,1370}, {36060,1323}, {35985,1272}, {35913,1216}, - {35844,1156}, {35779,1093}, {35718,1026}, {35660,955}, {35607,881}, {35558,804}, - {35514,725}, {35475,643}, {35440,559}, {35427,524}, {33779,524}, {33747,610}, - {33709,693}, {33666,773}, {33619,852}, {33567,927}, {33511,999}, {33451,1068}, - {33387,1133}, {33320,1195}, {33249,1252}, {33175,1305}, {33097,1354}, {33017,1398}, - {32935,1437}, {32851,1472}, {32764,1502}, {32676,1526}, {32587,1546}, {32497,1560}, - {32406,1569}, {32315,1573}, {32223,1571}, {32132,1564}, {32042,1552}, {31952,1535}, - {31864,1512}, {31777,1485}, {31691,1452}, {31608,1414}, {31527,1372}, {31449,1325}, - {31374,1274}, {31301,1218}, {31232,1158}, {31167,1094}, {31105,1027}, {31048,956}, - {30994,882}, {30945,805}, {30901,725}, {30861,643}, {30826,559}, {30814,524}, - {29166,524}, {29133,610}, {29095,693}, {29052,773}, {29005,852}, {28954,927}, - {28898,999}, {28838,1068}, {28774,1133}, {28706,1195}, {28635,1252}, {28561,1305}, - {28484,1354}, {28404,1398}, {28321,1437}, {28237,1472}, {28150,1502}, {28063,1526}, - {27973,1546}, {27883,1560}, {27792,1569}, {27701,1573}, {27610,1571}, {27519,1564}, - {27428,1552}, {27338,1535}, {27250,1512}, {27163,1485}, {27078,1452}, {26994,1414}, - {26914,1372}, {26835,1325}, {26760,1274}, {26687,1218}, {26618,1158}, {26553,1094}, - {26491,1027}, {26434,956}, {26380,882}, {26331,805}, {26287,725}, {26247,643}, - {26212,559}, {26200,524}, {24552,524}, {24519,610}, {24481,693}, {24439,773}, - {24391,852}, {24340,927}, {24284,999}, {24224,1068}, {24160,1133}, {24092,1195}, - {24021,1252}, {23947,1305}, {23870,1354}, {23790,1398}, {23708,1437}, {23623,1472}, - {23537,1502}, {23449,1526}, {23360,1546}, {23269,1560}, {23178,1569}, {23087,1573}, - {22996,1571}, {22905,1564}, {22814,1552}, {22725,1535}, {22636,1512}, {22549,1485}, - {22464,1452}, {22381,1414}, {22300,1372}, {22221,1325}, {22146,1274}, {22074,1218}, - {22005,1158}, {21939,1094}, {21878,1027}, {21820,956}, {21767,882}, {21718,805}, - {21673,725}, {21633,643}, {21599,559}, {21586,524}, {19938,524}, {19905,610}, - {19867,693}, {19825,773}, {19778,852}, {19726,927}, {19670,999}, {19610,1068}, - {19546,1133}, {19478,1195}, {19407,1252}, {19333,1305}, {19256,1354}, {19176,1398}, - {19094,1437}, {19009,1472}, {18923,1502}, {18835,1526}, {18746,1546}, {18656,1560}, - {18565,1569}, {18473,1573}, {18382,1571}, {18291,1564}, {18201,1552}, {18111,1535}, - {18022,1512}, {17935,1485}, {17850,1452}, {17767,1414}, {17686,1372}, {17608,1325}, - {17532,1274}, {17460,1218}, {17391,1158}, {17325,1094}, {17264,1027}, {17206,956}, - {17153,882}, {17104,805}, {17059,725}, {17020,643}, {16985,559}, {16972,524}, - {15324,524}, {15291,610}, {15254,693}, {15211,773}, {15164,852}, {15112,927}, - {15056,999}, {14996,1068}, {14932,1133}, {14865,1195}, {14794,1252}, {14719,1305}, - {14642,1354}, {14562,1398}, {14480,1437}, {14395,1472}, {14309,1502}, {14221,1526}, - {14132,1546}, {14042,1560}, {13951,1569}, {13860,1573}, {13768,1571}, {13677,1564}, - {13587,1552}, {13497,1535}, {13409,1512}, {13322,1485}, {13236,1452}, {13153,1414}, - {13072,1372}, {12994,1325}, {12918,1274}, {12846,1218}, {12777,1158}, {12712,1094}, - {12650,1027}, {12592,956}, {12539,882}, {12490,805}, {12446,725}, {12406,643}, - {12371,559}, {12358,524}, {10711,524}, {10678,610}, {10640,693}, {10597,773}, - {10550,852}, {10498,927}, {10442,999}, {10382,1068}, {10319,1133}, {10251,1195}, - {10180,1252}, {10106,1305}, {10028,1354}, {9949,1398}, {9866,1437}, {9782,1472}, - {9695,1502}, {9607,1526}, {9518,1546}, {9428,1560}, {9337,1569}, {9246,1573}, - {9155,1571}, {9064,1564}, {8973,1552}, {8883,1535}, {8795,1512}, {8708,1485}, - {8623,1452}, {8539,1414}, {8458,1372}, {8380,1325}, {8305,1274}, {8232,1218}, - {8163,1158}, {8098,1094}, {8036,1027}, {7979,956}, {7925,882}, {7876,805}, - {7832,725}, {7792,643}, {7757,559}, {7745,524}, {6097,524}, {6064,610}, - {6026,693}, {5983,773}, {5936,852}, {5885,927}, {5829,999}, {5769,1068}, - {5705,1133}, {5637,1195}, {5566,1252}, {5492,1305}, {5415,1354}, {5335,1398}, - {5252,1437}, {5168,1472}, {5082,1502}, {4994,1526}, {4904,1546}, {4814,1560}, - {4723,1569}, {4632,1573}, {4541,1571}, {4450,1564}, {4359,1552}, {4270,1535}, - {4181,1512}, {4094,1485}, {4009,1452}, {3926,1414}, {3845,1372}, {3766,1325}, - {3691,1274}, {3619,1218}, {3550,1158}, {3484,1094}, {3423,1027}, {3365,956}, - {3312,882}, {3263,805}, {3218,725}, {3178,643}, {3143,559}, {3131,524}, - {1483,524}, {1450,609}, {1413,692}, {1370,773}, {1323,851}, {1272,926}, - {1216,998}, {1156,1066}, {1093,1131}, {1026,1193}, {955,1250}, {881,1303}, - {804,1352}, {725,1396}, {643,1436}, {559,1470}}, - - {{-47877,-47877}, {84788,-47877}, {84788,81432}, {-47877,81432}} + MakePath({ + 524,1483, 524,2711, 610,2744, 693,2782, 773,2825, 852,2872, + 927,2924, 999,2980, 1068,3040, 1133,3103, 1195,3171, 1252,3242, + 1305,3316, 1354,3394, 1398,3473, 1437,3556, 1472,3640, 1502,3727, + 1526,3815, 1546,3904, 1560,3994, 1569,4085, 1573,4176, 1571,4267, + 1564,4358, 1552,4449, 1535,4539, 1512,4627, 1485,4714, 1452,4799, + 1414,4883, 1372,4964, 1325,5042, 1274,5117, 1218,5190, 1158,5259, + 1094,5324, 1027,5386, 956,5443, 882,5497, 805,5546, 725,5590, + 643,5630, 559,5665, 524,5677, 524,6906, 610,6939, 693,6977, + 773,7019, 852,7066, 927,7118, 999,7174, 1068,7234, 1133,7298, + 1195,7365, 1252,7436, 1305,7511, 1354,7588, 1398,7668, 1437,7750, + 1472,7835, 1502,7921, 1526,8009, 1546,8098, 1560,8188, 1569,8279, + 1573,8370, 1571,8462, 1564,8553, 1552,8643, 1535,8733, 1512,8821, + 1485,8908, 1452,8994, 1414,9077, 1372,9158, 1325,9236, 1274,9312, + 1218,9384, 1158,9453, 1094,9518, 1027,9580, 956,9638, 882,9691, + 805,9740, 725,9784, 643,9824, 559,9859, 524,9872, 524,11100, + 610,11133, 693,11171, 773,11213, 852,11261, 927,11312, 999,11368, + 1068,11428, 1133,11492, 1195,11560, 1252,11631, 1305,11705, 1354,11782, + 1398,11862, 1437,11945, 1472,12029, 1502,12115, 1526,12203, 1546,12293, + 1560,12383, 1569,12474, 1573,12565, 1571,12656, 1564,12747, 1552,12838, + 1535,12927, 1512,13016, 1485,13103, 1452,13188, 1414,13271, 1372,13352, + 1325,13431, 1274,13506, 1218,13578, 1158,13647, 1094,13713, 1027,13774, + 956,13832, 882,13885, 805,13934, 725,13979, 643,14019, 559,14053, + 524,14066, 524,15295, 610,15327, 693,15365, 773,15408, 852,15455, + 927,15507, 999,15563, 1068,15623, 1133,15687, 1195,15754, 1252,15825, + 1305,15899, 1354,15977, 1398,16057, 1437,16139, 1472,16223, 1502,16310, + 1526,16398, 1546,16487, 1560,16577, 1569,16668, 1573,16759, 1571,16850, + 1564,16942, 1552,17032, 1535,17122, 1512,17210, 1485,17297, 1452,17382, + 1414,17466, 1372,17547, 1325,17625, 1274,17700, 1218,17773, 1158,17842, + 1094,17907, 1027,17969, 956,18026, 882,18080, 805,18129, 725,18173, + 643,18213, 559,18248, 524,18260, 524,19489, 610,19522, 693,19560, + 773,19602, 852,19649, 927,19701, 999,19757, 1068,19817, 1133,19881, + 1195,19948, 1252,20019, 1305,20094, 1354,20171, 1398,20251, 1437,20333, + 1472,20418, 1502,20504, 1526,20592, 1546,20681, 1560,20771, 1569,20862, + 1573,20954, 1571,21045, 1564,21136, 1552,21226, 1535,21316, 1512,21404, + 1485,21492, 1452,21577, 1414,21660, 1372,21741, 1325,21819, 1274,21895, + 1218,21967, 1158,22036, 1094,22101, 1027,22163, 956,22221, 882,22274, + 805,22323, 725,22368, 643,22407, 559,22442, 524,22455, 524,23683, + 610,23716, 693,23754, 773,23797, 852,23844, 927,23895, 999,23951, + 1068,24011, 1133,24075, 1195,24143, 1252,24214, 1305,24288, 1354,24365, + 1398,24445, 1437,24528, 1472,24612, 1502,24698, 1526,24786, 1546,24876, + 1560,24966, 1569,25057, 1573,25148, 1571,25239, 1564,25330, 1552,25421, + 1535,25510, 1512,25599, 1485,25686, 1452,25771, 1414,25854, 1372,25935, + 1325,26014, 1274,26089, 1218,26161, 1158,26230, 1094,26296, 1027,26357, + 956,26415, 882,26468, 805,26517, 725,26562, 643,26602, 559,26636, + 524,26649, 524,27878, 610,27910, 693,27948, 773,27991, 852,28038, + 927,28090, 999,28146, 1068,28206, 1133,28270, 1195,28337, 1252,28408, + 1305,28482, 1354,28560, 1398,28640, 1437,28722, 1472,28806, 1502,28893, + 1526,28981, 1546,29070, 1560,29160, 1569,29251, 1573,29342, 1571,29434, + 1564,29525, 1552,29615, 1535,29705, 1512,29793, 1485,29880, 1452,29965, + 1414,30049, 1372,30130, 1325,30208, 1274,30283, 1218,30356, 1158,30425, + 1094,30490, 1027,30552, 956,30609, 882,30663, 805,30712, 725,30756, + 643,30796, 559,30831, 524,30843, 524,32072, 609,32105, 692,32142, + 773,32185, 851,32232, 926,32283, 998,32339, 1066,32398, 1131,32462, + 1193,32529, 1250,32600, 1303,32674, 1352,32751, 1396,32830, 1436,32912, + 1470,32996, 1483,33031, 3131,33031, 3164,32945, 3202,32862, 3244,32781, + 3291,32703, 3343,32628, 3399,32556, 3459,32487, 3523,32422, 3591,32360, + 3662,32303, 3736,32250, 3813,32201, 3893,32157, 3975,32117, 4060,32083, + 4146,32053, 4234,32028, 4323,32009, 4413,31995, 4504,31986, 4596,31982, + 4687,31984, 4778,31991, 4868,32003, 4958,32020, 5047,32043, 5134,32070, + 5219,32103, 5302,32141, 5383,32183, 5461,32230, 5537,32281, 5609,32337, + 5678,32397, 5744,32460, 5805,32528, 5863,32599, 5916,32673, 5965,32750, + 6010,32830, 6049,32912, 6084,32996, 6097,33031, 7745,33031, 7778,32945, + 7815,32862, 7858,32781, 7905,32703, 7957,32628, 8013,32556, 8073,32487, + 8137,32422, 8204,32360, 8275,32303, 8350,32250, 8427,32201, 8507,32157, + 8589,32117, 8674,32083, 8760,32053, 8848,32028, 8937,32009, 9027,31995, + 9118,31986, 9209,31982, 9301,31984, 9392,31991, 9482,32003, 9572,32020, + 9660,32043, 9747,32070, 9833,32103, 9916,32141, 9997,32183, 10075,32230, + 10151,32281, 10223,32337, 10292,32397, 10357,32460, 10419,32528, 10477,32599, + 10530,32673, 10579,32750, 10623,32830, 10663,32912, 10698,32996, 10711,33031, + 12358,33031, 12391,32945, 12429,32862, 12472,32781, 12519,32703, 12571,32628, + 12627,32556, 12687,32487, 12750,32422, 12818,32360, 12889,32303, 12963,32250, + 13041,32201, 13120,32157, 13203,32117, 13287,32083, 13374,32053, 13462,32028, + 13551,32009, 13641,31995, 13732,31986, 13823,31982, 13914,31984, 14005,31991, + 14096,32003, 14186,32020, 14274,32043, 14361,32070, 14446,32103, 14530,32141, + 14611,32183, 14689,32230, 14764,32281, 14837,32337, 14906,32397, 14971,32460, + 15033,32528, 15090,32599, 15144,32673, 15193,32750, 15237,32830, 15277,32912, + 15312,32996, 15324,33031, 16972,33031, 17005,32945, 17043,32862, 17086,32781, + 17133,32703, 17184,32628, 17240,32556, 17300,32487, 17364,32422, 17432,32360, + 17503,32303, 17577,32250, 17654,32201, 17734,32157, 17817,32117, 17901,32083, + 17987,32053, 18075,32028, 18165,32009, 18255,31995, 18346,31986, 18437,31982, + 18528,31984, 18619,31991, 18710,32003, 18799,32020, 18888,32043, 18975,32070, + 19060,32103, 19143,32141, 19224,32183, 19303,32230, 19378,32281, 19450,32337, + 19519,32397, 19585,32460, 19646,32528, 19704,32599, 19757,32673, 19806,32750, + 19851,32830, 19891,32912, 19926,32996, 19938,33031, 21586,33031, 21619,32945, + 21657,32862, 21699,32781, 21747,32703, 21798,32628, 21854,32556, 21914,32487, + 21978,32422, 22046,32360, 22117,32303, 22191,32250, 22268,32201, 22348,32157, + 22430,32117, 22515,32083, 22601,32053, 22689,32028, 22778,32009, 22869,31995, + 22959,31986, 23051,31982, 23142,31984, 23233,31991, 23324,32003, 23413,32020, + 23502,32043, 23589,32070, 23674,32103, 23757,32141, 23838,32183, 23916,32230, + 23992,32281, 24064,32337, 24133,32397, 24199,32460, 24260,32528, 24318,32599, + 24371,32673, 24420,32750, 24465,32830, 24504,32912, 24539,32996, 24552,33031, + 26200,33031, 26233,32945, 26271,32862, 26313,32781, 26360,32703, 26412,32628, + 26468,32556, 26528,32487, 26592,32422, 26659,32360, 26730,32303, 26805,32250, + 26882,32201, 26962,32157, 27044,32117, 27129,32083, 27215,32053, 27303,32028, + 27392,32009, 27482,31995, 27573,31986, 27664,31982, 27756,31984, 27847,31991, + 27937,32003, 28027,32020, 28115,32043, 28202,32070, 28288,32103, 28371,32141, + 28452,32183, 28530,32230, 28606,32281, 28678,32337, 28747,32397, 28812,32460, + 28874,32528, 28932,32599, 28985,32673, 29034,32750, 29078,32830, 29118,32912, + 29153,32996, 29166,33031, 30814,33031, 30847,32945, 30884,32862, 30927,32781, + 30974,32703, 31026,32628, 31082,32556, 31142,32487, 31206,32422, 31273,32360, + 31344,32303, 31418,32250, 31496,32201, 31576,32157, 31658,32117, 31742,32083, + 31829,32053, 31917,32028, 32006,32009, 32096,31995, 32187,31986, 32278,31982, + 32370,31984, 32461,31991, 32551,32003, 32641,32020, 32729,32043, 32816,32070, + 32902,32103, 32985,32141, 33066,32183, 33144,32230, 33219,32281, 33292,32337, + 33361,32397, 33426,32460, 33488,32528, 33545,32599, 33599,32673, 33648,32750, + 33692,32830, 33732,32912, 33767,32996, 33779,33031, 35427,33031, 35460,32946, + 35498,32863, 35540,32782, 35587,32704, 35639,32629, 35694,32557, 35754,32489, + 35818,32423, 35885,32362, 35956,32305, 36029,32252, 36106,32203, 36186,32159, + 36268,32119, 36352,32084, 36386,32072, 36386,30843, 36301,30810, 36218,30773, + 36137,30730, 36059,30683, 35983,30631, 35911,30575, 35842,30515, 35777,30451, + 35716,30384, 35658,30313, 35605,30239, 35557,30161, 35512,30081, 35473,29999, + 35438,29915, 35409,29828, 35384,29740, 35364,29651, 35350,29561, 35341,29470, + 35338,29379, 35339,29287, 35346,29196, 35358,29106, 35376,29016, 35398,28928, + 35426,28841, 35458,28755, 35496,28672, 35538,28591, 35585,28513, 35637,28438, + 35692,28365, 35752,28296, 35816,28231, 35883,28169, 35954,28112, 36028,28058, + 36105,28009, 36185,27965, 36267,27925, 36352,27890, 36386,27878, 36386,26649, + 36301,26616, 36218,26578, 36137,26536, 36059,26489, 35983,26437, 35911,26381, + 35842,26321, 35777,26257, 35716,26189, 35658,26118, 35605,26044, 35557,25967, + 35512,25887, 35473,25805, 35438,25720, 35409,25634, 35384,25546, 35364,25457, + 35350,25366, 35341,25276, 35338,25184, 35339,25093, 35346,25002, 35358,24912, + 35376,24822, 35398,24733, 35426,24646, 35458,24561, 35496,24478, 35538,24397, + 35585,24319, 35637,24243, 35692,24171, 35752,24102, 35816,24036, 35883,23975, + 35954,23917, 36028,23864, 36105,23815, 36185,23770, 36267,23731, 36352,23696, + 36386,23683, 36386,22455, 36301,22422, 36218,22384, 36137,22341, 36059,22294, + 35983,22243, 35911,22187, 35842,22127, 35777,22063, 35716,21995, 35658,21924, + 35605,21850, 35557,21773, 35512,21693, 35473,21610, 35438,21526, 35409,21440, + 35384,21352, 35364,21262, 35350,21172, 35341,21081, 35338,20990, 35339,20899, + 35346,20808, 35358,20717, 35376,20628, 35398,20539, 35426,20452, 35458,20367, + 35496,20284, 35538,20203, 35585,20124, 35637,20049, 35692,19976, 35752,19907, + 35816,19842, 35883,19780, 35954,19723, 36028,19669, 36105,19620, 36185,19576, + 36267,19536, 36352,19501, 36386,19489, 36386,18260, 36301,18227, 36218,18190, + 36137,18147, 36059,18100, 35983,18048, 35911,17992, 35842,17932, 35777,17868, + 35716,17801, 35658,17730, 35605,17655, 35557,17578, 35512,17498, 35473,17416, + 35438,17332, 35409,17245, 35384,17157, 35364,17068, 35350,16978, 35341,16887, + 35338,16796, 35339,16704, 35346,16613, 35358,16523, 35376,16433, 35398,16345, + 35426,16258, 35458,16172, 35496,16089, 35538,16008, 35585,15930, 35637,15854, + 35692,15782, 35752,15713, 35816,15648, 35883,15586, 35954,15529, 36028,15475, + 36105,15426, 36185,15382, 36267,15342, 36352,15307, 36386,15295, 36386,14066, + 36301,14033, 36218,13995, 36137,13953, 36059,13906, 35983,13854, 35911,13798, + 35842,13738, 35777,13674, 35716,13606, 35658,13535, 35605,13461, 35557,13384, + 35512,13304, 35473,13222, 35438,13137, 35409,13051, 35384,12963, 35364,12874, + 35350,12783, 35341,12693, 35338,12601, 35339,12510, 35346,12419, 35358,12328, + 35376,12239, 35398,12150, 35426,12063, 35458,11978, 35496,11895, 35538,11814, + 35585,11736, 35637,11660, 35692,11588, 35752,11519, 35816,11453, 35883,11392, + 35954,11334, 36028,11281, 36105,11232, 36185,11187, 36267,11148, 36352,11113, + 36386,11100, 36386,9872, 36301,9839, 36218,9801, 36137,9758, 36059,9711, + 35983,9660, 35911,9604, 35842,9544, 35777,9480, 35716,9412, 35658,9341, + 35605,9267, 35557,9190, 35512,9110, 35473,9027, 35438,8943, 35409,8856, + 35384,8769, 35364,8679, 35350,8589, 35341,8498, 35338,8407, 35339,8316, + 35346,8225, 35358,8134, 35376,8045, 35398,7956, 35426,7869, 35458,7784, + 35496,7700, 35538,7620, 35585,7541, 35637,7466, 35692,7393, 35752,7324, + 35816,7259, 35883,7197, 35954,7140, 36028,7086, 36105,7037, 36185,6993, + 36267,6953, 36352,6918, 36386,6906, 36386,5677, 36301,5644, 36218,5607, + 36137,5564, 36059,5517, 35983,5465, 35911,5409, 35842,5349, 35777,5285, + 35716,5218, 35658,5147, 35605,5072, 35557,4995, 35512,4915, 35473,4833, + 35438,4748, 35409,4662, 35384,4574, 35364,4485, 35350,4395, 35341,4304, + 35338,4213, 35339,4121, 35346,4030, 35358,3940, 35376,3850, 35398,3762, + 35426,3675, 35458,3589, 35496,3506, 35538,3425, 35585,3347, 35637,3271, + 35692,3199, 35752,3130, 35816,3065, 35883,3003, 35954,2945, 36028,2892, + 36105,2843, 36185,2799, 36267,2759, 36352,2724, 36386,2711, 36386,1483, + 36301,1450, 36218,1413, 36138,1370, 36060,1323, 35985,1272, 35913,1216, + 35844,1156, 35779,1093, 35718,1026, 35660,955, 35607,881, 35558,804, + 35514,725, 35475,643, 35440,559, 35427,524, 33779,524, 33747,610, + 33709,693, 33666,773, 33619,852, 33567,927, 33511,999, 33451,1068, + 33387,1133, 33320,1195, 33249,1252, 33175,1305, 33097,1354, 33017,1398, + 32935,1437, 32851,1472, 32764,1502, 32676,1526, 32587,1546, 32497,1560, + 32406,1569, 32315,1573, 32223,1571, 32132,1564, 32042,1552, 31952,1535, + 31864,1512, 31777,1485, 31691,1452, 31608,1414, 31527,1372, 31449,1325, + 31374,1274, 31301,1218, 31232,1158, 31167,1094, 31105,1027, 31048,956, + 30994,882, 30945,805, 30901,725, 30861,643, 30826,559, 30814,524, + 29166,524, 29133,610, 29095,693, 29052,773, 29005,852, 28954,927, + 28898,999, 28838,1068, 28774,1133, 28706,1195, 28635,1252, 28561,1305, + 28484,1354, 28404,1398, 28321,1437, 28237,1472, 28150,1502, 28063,1526, + 27973,1546, 27883,1560, 27792,1569, 27701,1573, 27610,1571, 27519,1564, + 27428,1552, 27338,1535, 27250,1512, 27163,1485, 27078,1452, 26994,1414, + 26914,1372, 26835,1325, 26760,1274, 26687,1218, 26618,1158, 26553,1094, + 26491,1027, 26434,956, 26380,882, 26331,805, 26287,725, 26247,643, + 26212,559, 26200,524, 24552,524, 24519,610, 24481,693, 24439,773, + 24391,852, 24340,927, 24284,999, 24224,1068, 24160,1133, 24092,1195, + 24021,1252, 23947,1305, 23870,1354, 23790,1398, 23708,1437, 23623,1472, + 23537,1502, 23449,1526, 23360,1546, 23269,1560, 23178,1569, 23087,1573, + 22996,1571, 22905,1564, 22814,1552, 22725,1535, 22636,1512, 22549,1485, + 22464,1452, 22381,1414, 22300,1372, 22221,1325, 22146,1274, 22074,1218, + 22005,1158, 21939,1094, 21878,1027, 21820,956, 21767,882, 21718,805, + 21673,725, 21633,643, 21599,559, 21586,524, 19938,524, 19905,610, + 19867,693, 19825,773, 19778,852, 19726,927, 19670,999, 19610,1068, + 19546,1133, 19478,1195, 19407,1252, 19333,1305, 19256,1354, 19176,1398, + 19094,1437, 19009,1472, 18923,1502, 18835,1526, 18746,1546, 18656,1560, + 18565,1569, 18473,1573, 18382,1571, 18291,1564, 18201,1552, 18111,1535, + 18022,1512, 17935,1485, 17850,1452, 17767,1414, 17686,1372, 17608,1325, + 17532,1274, 17460,1218, 17391,1158, 17325,1094, 17264,1027, 17206,956, + 17153,882, 17104,805, 17059,725, 17020,643, 16985,559, 16972,524, + 15324,524, 15291,610, 15254,693, 15211,773, 15164,852, 15112,927, + 15056,999, 14996,1068, 14932,1133, 14865,1195, 14794,1252, 14719,1305, + 14642,1354, 14562,1398, 14480,1437, 14395,1472, 14309,1502, 14221,1526, + 14132,1546, 14042,1560, 13951,1569, 13860,1573, 13768,1571, 13677,1564, + 13587,1552, 13497,1535, 13409,1512, 13322,1485, 13236,1452, 13153,1414, + 13072,1372, 12994,1325, 12918,1274, 12846,1218, 12777,1158, 12712,1094, + 12650,1027, 12592,956, 12539,882, 12490,805, 12446,725, 12406,643, + 12371,559, 12358,524, 10711,524, 10678,610, 10640,693, 10597,773, + 10550,852, 10498,927, 10442,999, 10382,1068, 10319,1133, 10251,1195, + 10180,1252, 10106,1305, 10028,1354, 9949,1398, 9866,1437, 9782,1472, + 9695,1502, 9607,1526, 9518,1546, 9428,1560, 9337,1569, 9246,1573, + 9155,1571, 9064,1564, 8973,1552, 8883,1535, 8795,1512, 8708,1485, + 8623,1452, 8539,1414, 8458,1372, 8380,1325, 8305,1274, 8232,1218, + 8163,1158, 8098,1094, 8036,1027, 7979,956, 7925,882, 7876,805, + 7832,725, 7792,643, 7757,559, 7745,524, 6097,524, 6064,610, + 6026,693, 5983,773, 5936,852, 5885,927, 5829,999, 5769,1068, + 5705,1133, 5637,1195, 5566,1252, 5492,1305, 5415,1354, 5335,1398, + 5252,1437, 5168,1472, 5082,1502, 4994,1526, 4904,1546, 4814,1560, + 4723,1569, 4632,1573, 4541,1571, 4450,1564, 4359,1552, 4270,1535, + 4181,1512, 4094,1485, 4009,1452, 3926,1414, 3845,1372, 3766,1325, + 3691,1274, 3619,1218, 3550,1158, 3484,1094, 3423,1027, 3365,956, + 3312,882, 3263,805, 3218,725, 3178,643, 3143,559, 3131,524, + 1483,524, 1450,609, 1413,692, 1370,773, 1323,851, 1272,926, + 1216,998, 1156,1066, 1093,1131, 1026,1193, 955,1250, 881,1303, + 804,1352, 725,1396, 643,1436, 559,1470 + }), + MakePath({ -47877,-47877, 84788,-47877, 84788,81432, -47877,81432 }) }; Paths64 solution = InflatePaths(subject, -10000, JoinType::Round, EndType::Polygon); diff --git a/CPP/Utils/ClipFileSave.cpp b/CPP/Utils/ClipFileSave.cpp index ad0755d8..305484c6 100644 --- a/CPP/Utils/ClipFileSave.cpp +++ b/CPP/Utils/ClipFileSave.cpp @@ -2,13 +2,13 @@ // Functions load clipping operations from text files //------------------------------------------------------------------------------ -#include "ClipFileLoad.h" #include "ClipFileSave.h" #include #include -using namespace std; -using namespace Clipper2Lib; +namespace Clipper2Lib { + + using namespace std; //------------------------------------------------------------------------------ // Boyer Moore Horspool Search @@ -247,8 +247,8 @@ static bool GetInt(string::const_iterator& s_it, } bool SaveTest(const std::string& filename, bool append, - Clipper2Lib::Paths64* subj, Clipper2Lib::Paths64* subj_open, Clipper2Lib::Paths64* clip, - int64_t area, int64_t count, Clipper2Lib::ClipType ct, Clipper2Lib::FillRule fr) + 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; @@ -258,7 +258,7 @@ bool SaveTest(const std::string& filename, bool append, { ifstream file; file.open(filename, std::ios::binary); - if (!file) return false; + if (!file || !file.good()) return false; BMH_Search bmh = BMH_Search(file, "CAPTION:"); while (bmh.FindNext()) ; if (bmh.LastFound()) @@ -322,3 +322,5 @@ bool SaveTest(const std::string& filename, bool append, source.close(); return true; } + +} //end namespace diff --git a/CPP/Utils/ClipFileSave.h b/CPP/Utils/ClipFileSave.h index 9aa253ae..2624b56c 100644 --- a/CPP/Utils/ClipFileSave.h +++ b/CPP/Utils/ClipFileSave.h @@ -5,10 +5,14 @@ #ifndef CLIPPER_TEST_SAVE_H #define CLIPPER_TEST_SAVE_H -#include "clipper2/clipper.h" +#include "ClipFileLoad.h" -bool SaveTest(const std::string& filename, bool append, - Clipper2Lib::Paths64* subj, Clipper2Lib::Paths64* subj_open, Clipper2Lib::Paths64* clip, - int64_t area, int64_t count, Clipper2Lib::ClipType ct, Clipper2Lib::FillRule fr); +namespace Clipper2Lib { + + bool SaveTest(const std::string& filename, bool append, + const Paths64* subj, const Paths64* subj_open, const Paths64* clip, + int64_t area, int64_t count, ClipType ct, FillRule fr); + +} //end namespace #endif //CLIPPER_TEST_SAVE_H diff --git a/CPP/Utils/clipper.svg.cpp b/CPP/Utils/clipper.svg.cpp index 337a07ab..c0a8754d 100644 --- a/CPP/Utils/clipper.svg.cpp +++ b/CPP/Utils/clipper.svg.cpp @@ -140,7 +140,7 @@ namespace Clipper2Lib { bool SvgWriter::SaveToFile(const std::string &filename, int max_width, int max_height, int margin) { - RectD rec = MaxInvalidRectD; + RectD rec = InvalidRectD; for (const PathInfo* pi : path_infos) for (const PathD& path : pi->paths_) for (const PointD& pt : path){ diff --git a/CSharp/Clipper2Lib/Clipper.Core.cs b/CSharp/Clipper2Lib/Clipper.Core.cs index b1f95333..6e35fb8a 100644 --- a/CSharp/Clipper2Lib/Clipper.Core.cs +++ b/CSharp/Clipper2Lib/Clipper.Core.cs @@ -342,6 +342,11 @@ public readonly bool IsEmpty() return bottom <= top || right <= left; } + public readonly bool IsValid() + { + return left < long.MaxValue; + } + public readonly Point64 MidPoint() { return new Point64((left + right) /2, (top + bottom)/2); diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index a1bd64c7..32d6961b 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 October 2023 * +* Date : 8 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -10,6 +10,8 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using Clipper2Lib; namespace Clipper2Lib { @@ -36,26 +38,46 @@ public class ClipperOffset private class Group { internal Paths64 inPaths; - internal Path64 outPath; - internal Paths64 outPaths; + internal List boundsList; internal JoinType joinType; internal EndType endType; internal bool pathsReversed; + internal int lowestPathIdx; public Group(Paths64 paths, JoinType joinType, EndType endType = EndType.Polygon) { - inPaths = new Paths64(paths); this.joinType = joinType; this.endType = endType; - outPath = new Path64(); - outPaths = new Paths64(); + + bool isJoined = ((endType == EndType.Polygon) || (endType == EndType.Joined)); + inPaths = new Paths64(paths.Count); + foreach(Path64 path in paths) + inPaths.Add(Clipper.StripDuplicates(path, isJoined)); + + boundsList = new List(); + GetMultiBounds(inPaths, boundsList, endType); + lowestPathIdx = GetLowestPathIdx(boundsList); pathsReversed = false; + if (endType == EndType.Polygon) + pathsReversed = Clipper.Area(inPaths[lowestPathIdx]) < 0; } } private static readonly double Tolerance = 1.0E-12; + private static readonly Rect64 InvalidRect64 = + new Rect64(long.MaxValue, long.MaxValue, long.MinValue, long.MinValue); + private static readonly RectD InvalidRectD = + new RectD(double.MaxValue, double.MaxValue, double.MinValue, double.MinValue); + private static readonly long MAX_COORD = long.MaxValue >> 2; + private static readonly long MIN_COORD = -MAX_COORD; + + private static readonly string + coord_range_error = "Error: Coordinate range."; + private readonly List _groupList = new List(); + private readonly Path64 inPath = new Path64(); + private Path64 pathOut = new Path64(); private readonly PathD _normals = new PathD(); private readonly Paths64 _solution = new Paths64(); private double _groupDelta; //*0.5 for open paths; *-1.0 for negative areas @@ -112,26 +134,47 @@ public void AddPaths(Paths64 paths, JoinType joinType, EndType endType) _groupList.Add(new Group(paths, joinType, endType)); } + private int CalcSolutionCapacity() + { + int result = 0; + foreach (Group g in _groupList) + result += (g.endType == EndType.Joined) ? g.inPaths.Count * 2 : g.inPaths.Count; + return result; + } + private void ExecuteInternal(double delta) { _solution.Clear(); if (_groupList.Count == 0) return; + _solution.Capacity = CalcSolutionCapacity(); + // make sure the offset delta is significant if (Math.Abs(delta) < 0.5) { foreach (Group group in _groupList) foreach (Path64 path in group.inPaths) _solution.Add(path); + return; } - else - { - _delta = delta; - _mitLimSqr = (MiterLimit <= 1 ? - 2.0 : 2.0 / Clipper.Sqr(MiterLimit)); - foreach (Group group in _groupList) - DoGroupOffset(group); - } + _delta = delta; + _mitLimSqr = (MiterLimit <= 1 ? + 2.0 : 2.0 / Clipper.Sqr(MiterLimit)); + + foreach (Group group in _groupList) + DoGroupOffset(group); + } + + internal bool CheckPathsReversed() + { + bool result = false; + foreach (Group g in _groupList) + if (g.endType == EndType.Polygon) + { + result = g.pathsReversed; + break; + } + return result; } public void Execute(double delta, Paths64 solution) @@ -140,44 +183,39 @@ public void Execute(double delta, Paths64 solution) ExecuteInternal(delta); if (_groupList.Count == 0) return; + bool pathsReversed = CheckPathsReversed(); + FillRule fillRule = pathsReversed ? FillRule.Negative : FillRule.Positive; + // clean up self-intersections ... - Clipper64 c = new Clipper64() - { - PreserveCollinear = PreserveCollinear, - // the solution should retain the orientation of the input - ReverseSolution = ReverseSolution != _groupList[0].pathsReversed - }; + Clipper64 c = new Clipper64(); + c.PreserveCollinear = PreserveCollinear; + // the solution should retain the orientation of the input + c.ReverseSolution = ReverseSolution != pathsReversed; #if USINGZ c.ZCallback = ZCallback; #endif c.AddSubject(_solution); - if (_groupList[0].pathsReversed) - c.Execute(ClipType.Union, FillRule.Negative, solution); - else - c.Execute(ClipType.Union, FillRule.Positive, solution); + c.Execute(ClipType.Union, fillRule, solution); } - public void Execute(double delta, PolyTree64 polytree) + public void Execute(double delta, PolyTree64 solutionTree) { - polytree.Clear(); + solutionTree.Clear(); ExecuteInternal(delta); if (_groupList.Count == 0) return; + bool pathsReversed = CheckPathsReversed(); + FillRule fillRule = pathsReversed ? FillRule.Negative : FillRule.Positive; // clean up self-intersections ... - Clipper64 c = new Clipper64() - { - PreserveCollinear = PreserveCollinear, - // the solution should retain the orientation of the input - ReverseSolution = ReverseSolution != _groupList[0].pathsReversed - }; + Clipper64 c = new Clipper64(); + c.PreserveCollinear = PreserveCollinear; + // the solution should normally retain the orientation of the input + c.ReverseSolution = ReverseSolution != pathsReversed; #if USINGZ c.ZCallback = ZCallback; #endif c.AddSubject(_solution); - if (_groupList[0].pathsReversed) - c.Execute(ClipType.Union, FillRule.Negative, polytree); - else - c.Execute(ClipType.Union, FillRule.Positive, polytree); + c.Execute(ClipType.Union, fillRule, solutionTree); } @@ -200,28 +238,64 @@ public void Execute(DeltaCallback64 deltaCallback, Paths64 solution) DeltaCallback = deltaCallback; Execute(1.0, solution); } - private static void GetBoundsAndLowestPolyIdx(Paths64 paths, - out int index, out Rect64 rec) + + internal static void GetMultiBounds(Paths64 paths, List boundsList, EndType endType) { - rec = new Rect64(false); // ie invalid rect - long lpX = long.MinValue; - index = -1; - for (int i = 0; i < paths.Count; i++) - foreach (Point64 pt in paths[i]) + + int minPathLen = (endType == EndType.Polygon) ? 3 : 1; + boundsList.Capacity = paths.Count; + foreach (Path64 path in paths) + { + if (path.Count < minPathLen) { - if (pt.Y >= rec.bottom) - { - if (pt.Y > rec.bottom || pt.X < lpX) - { - index = i; - lpX = pt.X; - rec.bottom = pt.Y; - } - } - else if (pt.Y < rec.top) rec.top = pt.Y; - if (pt.X > rec.right) rec.right = pt.X; - else if (pt.X < rec.left) rec.left = pt.X; + boundsList.Add(InvalidRect64); + continue; } + + Point64 pt1 = path[0]; + Rect64 r = new Rect64(pt1.X, pt1.Y, pt1.X, pt1.Y); + foreach (Point64 pt in path) + { + if (pt.Y > r.bottom) r.bottom = pt.Y; + else if (pt.Y < r.top) r.top = pt.Y; + if (pt.X > r.right) r.right = pt.X; + else if (pt.X < r.left) r.left = pt.X; + } + boundsList.Add(r); + } + } + + internal static bool ValidateBounds(List boundsList, double delta) + { + int int_delta = (int)delta; + + Point64 botPt = new Point64(int.MaxValue, int.MinValue); + foreach (Rect64 r in boundsList) + { + if (!r.IsValid()) continue; // ignore invalid paths + else if (r.left < MIN_COORD + int_delta || + r.right > MAX_COORD + int_delta || + r.top < MIN_COORD + int_delta || + r.bottom > MAX_COORD + int_delta) return false; + } + return true; + } + + internal static int GetLowestPathIdx(List boundsList) + { + int result = -1; + Point64 botPt = new Point64(long.MaxValue, long.MinValue); + for (int i = 0; i < boundsList.Count; i++) + { + Rect64 r = boundsList[i]; + if (!r.IsValid()) continue; // ignore invalid paths + else if (r.bottom > botPt.Y || (r.bottom == botPt.Y && r.left < botPt.X)) + { + botPt = new Point64(r.left, r.bottom); + result = i; + } + } + return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -326,7 +400,7 @@ private PointD GetPerpendicD(Point64 pt, PointD norm) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DoBevel(Group group, Path64 path, int j, int k) + private void DoBevel(Path64 path, int j, int k) { Point64 pt1, pt2; if (j == k) @@ -340,12 +414,12 @@ private void DoBevel(Group group, Path64 path, int j, int k) pt1 = new Point64(path[j].X + _groupDelta * _normals[k].x, path[j].Y + _groupDelta * _normals[k].y); pt2 = new Point64(path[j].X + _groupDelta * _normals[j].x, path[j].Y + _groupDelta * _normals[j].y); } - group.outPath.Add(pt1); - group.outPath.Add(pt2); + pathOut.Add(pt1); + pathOut.Add(pt2); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DoSquare(Group group, Path64 path, int j, int k) + private void DoSquare(Path64 path, int j, int k) { PointD vec; if (j == k) @@ -380,8 +454,8 @@ private void DoSquare(Group group, Path64 path, int j, int k) pt.z = ptQ.z; #endif //get the second intersect point through reflecion - group.outPath.Add(new Point64(ReflectPoint(pt, ptQ))); - group.outPath.Add(new Point64(pt)); + pathOut.Add(new Point64(ReflectPoint(pt, ptQ))); + pathOut.Add(new Point64(pt)); } else { @@ -390,9 +464,9 @@ private void DoSquare(Group group, Path64 path, int j, int k) #if USINGZ pt.z = ptQ.z; #endif - group.outPath.Add(new Point64(pt)); + pathOut.Add(new Point64(pt)); //get the second intersect point through reflecion - group.outPath.Add(new Point64(ReflectPoint(pt, ptQ))); + pathOut.Add(new Point64(ReflectPoint(pt, ptQ))); } } @@ -401,19 +475,19 @@ private void DoMiter(Group group, Path64 path, int j, int k, double cosA) { double q = _groupDelta / (cosA + 1); #if USINGZ - group.outPath.Add(new Point64( + pathOut.Add(new Point64( path[j].X + (_normals[k].x + _normals[j].x) * q, path[j].Y + (_normals[k].y + _normals[j].y) * q, path[j].Z)); #else - group.outPath.Add(new Point64( + pathOut.Add(new Point64( path[j].X + (_normals[k].x + _normals[j].x) * q, path[j].Y + (_normals[k].y + _normals[j].y) * q)); #endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DoRound(Group group, Path64 path, int j, int k, double angle) + private void DoRound(Path64 path, int j, int k, double angle) { if (DeltaCallback != null) { @@ -434,9 +508,9 @@ private void DoRound(Group group, Path64 path, int j, int k, double angle) PointD offsetVec = new PointD(_normals[k].x * _groupDelta, _normals[k].y * _groupDelta); if (j == k) offsetVec.Negate(); #if USINGZ - group.outPath.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y, pt.Z)); + pathOut.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y, pt.Z)); #else - group.outPath.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y)); + pathOut.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y)); #endif int steps = (int) Math.Ceiling(_stepsPerRad * Math.Abs(angle)); for (int i = 1; i < steps; i++) // ie 1 less than steps @@ -444,12 +518,12 @@ private void DoRound(Group group, Path64 path, int j, int k, double angle) offsetVec = new PointD(offsetVec.x * _stepCos - _stepSin * offsetVec.y, offsetVec.x * _stepSin + offsetVec.y * _stepCos); #if USINGZ - group.outPath.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y, pt.Z)); + pathOut.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y, pt.Z)); #else - group.outPath.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y)); + pathOut.Add(new Point64(pt.X + offsetVec.x, pt.Y + offsetVec.y)); #endif } - group.outPath.Add(GetPerpendic(pt, _normals[j])); + pathOut.Add(GetPerpendic(pt, _normals[j])); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -483,18 +557,18 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) } if (Math.Abs(_groupDelta) < Tolerance) { - group.outPath.Add(path[j]); + pathOut.Add(path[j]); return; } if (cosA > -0.99 && (sinA * _groupDelta < 0)) // test for concavity first (#593) { // is concave - group.outPath.Add(GetPerpendic(path[j], _normals[k])); + pathOut.Add(GetPerpendic(path[j], _normals[k])); // this extra point is the only (simple) way to ensure that // path reversals are fully cleaned with the trailing clipper - group.outPath.Add(path[j]); // (#405) - group.outPath.Add(GetPerpendic(path[j], _normals[j])); + pathOut.Add(path[j]); // (#405) + pathOut.Add(GetPerpendic(path[j], _normals[j])); } else if (cosA > 0.999) DoMiter(group, path, j, k, cosA); @@ -502,15 +576,15 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) { // miter unless the angle is so acute the miter would exceeds ML if (cosA > _mitLimSqr - 1) DoMiter(group, path, j, k, cosA); - else DoSquare(group, path, j, k); + else DoSquare(path, j, k); } else if (cosA > 0.99 || _joinType == JoinType.Bevel) //angle less than 8 degrees or a squared join - DoBevel(group, path, j, k); + DoBevel(path, j, k); else if (_joinType == JoinType.Round) - DoRound(group, path, j, k, Math.Atan2(sinA, cosA)); + DoRound(path, j, k, Math.Atan2(sinA, cosA)); else - DoSquare(group, path, j, k); + DoSquare(path, j, k); k = j; } @@ -518,22 +592,11 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void OffsetPolygon(Group group, Path64 path) { - // when the path is contracting, make sure - // there is sufficient space to do so. //#593 - //nb: this will have a small impact on performance - double a = Clipper.Area(path); - if ((a < 0) != (_groupDelta < 0)) - { - Rect64 rec = Clipper.GetBounds(path); - double offsetMinDim = Math.Abs(_groupDelta) * 2; - if (offsetMinDim > rec.Width || offsetMinDim > rec.Height) return; - } - - group.outPath = new Path64(); + pathOut = new Path64(); int cnt = path.Count, prev = cnt - 1; for (int i = 0; i < cnt; i++) OffsetPoint(group, path, i, ref prev); - group.outPaths.Add(group.outPath); + _solution.Add(pathOut); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -547,7 +610,7 @@ private void OffsetOpenJoined(Group group, Path64 path) private void OffsetOpenPath(Group group, Path64 path) { - group.outPath = new Path64(); + pathOut = new Path64(); int highI = path.Count - 1; if (DeltaCallback != null) @@ -555,18 +618,18 @@ private void OffsetOpenPath(Group group, Path64 path) // do the line start cap if (Math.Abs(_groupDelta) < Tolerance) - group.outPath.Add(path[0]); + pathOut.Add(path[0]); else switch (_endType) { case EndType.Butt: - DoBevel(group, path, 0, 0); + DoBevel(path, 0, 0); break; case EndType.Round: - DoRound(group, path, 0, 0, Math.PI); + DoRound(path, 0, 0, Math.PI); break; default: - DoSquare(group, path, 0, 0); + DoSquare(path, 0, 0); break; } @@ -583,18 +646,18 @@ private void OffsetOpenPath(Group group, Path64 path) _groupDelta = DeltaCallback(path, _normals, highI, highI); // do the line end cap if (Math.Abs(_groupDelta) < Tolerance) - group.outPath.Add(path[highI]); + pathOut.Add(path[highI]); else switch (_endType) { case EndType.Butt: - DoBevel(group, path, highI, highI); + DoBevel(path, highI, highI); break; case EndType.Round: - DoRound(group, path, highI, highI, Math.PI); + DoRound(path, highI, highI, Math.PI); break; default: - DoSquare(group, path, highI, highI); + DoSquare(path, highI, highI); break; } @@ -602,35 +665,28 @@ private void OffsetOpenPath(Group group, Path64 path) for (int i = highI, k = 0; i > 0; i--) OffsetPoint(group, path, i, ref k); - group.outPaths.Add(group.outPath); + _solution.Add(pathOut); } private void DoGroupOffset(Group group) { if (group.endType == EndType.Polygon) { - // the lowermost polygon must be an outer polygon. So we can use that as the - // designated orientation for outer polygons (needed for tidy-up clipping) - GetBoundsAndLowestPolyIdx(group.inPaths, - out int lowestIdx, out Rect64 grpBounds); - if (lowestIdx < 0) return; - double area = Clipper.Area(group.inPaths[lowestIdx]); - //if (area == 0) return; // this is probably unhelpful (#430) - group.pathsReversed = (area < 0); - if (group.pathsReversed) _groupDelta = -_delta; - else _groupDelta = _delta; + if (group.lowestPathIdx < 0) return; + //if (area == 0) return; // probably unhelpful (#430) + _groupDelta = (group.pathsReversed) ? -_delta : _delta; } else - { - group.pathsReversed = false; _groupDelta = Math.Abs(_delta) * 0.5; - } + double absDelta = Math.Abs(_groupDelta); + if (!ValidateBounds(group.boundsList, absDelta)) + throw new Exception(coord_range_error); + _joinType = group.joinType; _endType = group.endType; - if (DeltaCallback == null && - (group.joinType == JoinType.Round || group.endType == EndType.Round)) + if (group.joinType == JoinType.Round || group.endType == EndType.Round) { // calculate a sensible number of steps (for 360 deg for the given offset // arcTol - when fArcTolerance is undefined (0), the amount of @@ -647,59 +703,59 @@ private void DoGroupOffset(Group group) _stepsPerRad = stepsPer360 / (2 * Math.PI); } - bool isJoined = - (group.endType == EndType.Joined) || - (group.endType == EndType.Polygon); - + int i = 0; foreach (Path64 p in group.inPaths) { - Path64 path = Clipper.StripDuplicates(p, isJoined); - int cnt = path.Count; + Rect64 pathBounds = group.boundsList[i++]; + if (!pathBounds.IsValid()) continue; + + int cnt = p.Count; if ((cnt == 0) || ((cnt < 3) && (_endType == EndType.Polygon))) continue; + pathOut = new Path64(); if (cnt == 1) { - group.outPath = new Path64(); + Point64 pt = p[0]; + // 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); - group.outPath = Clipper.Ellipse(path[0], r, r, steps); + pathOut = Clipper.Ellipse(pt, r, r, steps); #if USINGZ - group.outPath = InternalClipper.SetZ(group.outPath, path[0].Z); + pathOut = InternalClipper.SetZ(pathOut, pt.Z); #endif } else { int d = (int) Math.Ceiling(_groupDelta); - Rect64 r = new Rect64(path[0].X - d, path[0].Y - d, - path[0].X + d, path[0].Y + d); - group.outPath = r.AsPath(); + Rect64 r = new Rect64(pt.X - d, pt.Y - d, pt.X + d, pt.Y + d); + pathOut = r.AsPath(); #if USINGZ - group.outPath = InternalClipper.SetZ(group.outPath, path[0].Z); + pathOut = InternalClipper.SetZ(pathOut, pt.Z); #endif } - group.outPaths.Add(group.outPath); - } - else - { - if (cnt == 2 && group.endType == EndType.Joined) - { - if (group.joinType == JoinType.Round) - _endType = EndType.Round; - else - _endType = EndType.Square; - } - BuildNormals(path); - if (_endType == EndType.Polygon) OffsetPolygon(group, path); - else if (_endType == EndType.Joined) OffsetOpenJoined(group, path); - else OffsetOpenPath(group, path); - } + _solution.Add(pathOut); + continue; + } // end of offsetting a single (open path) point + + // when shrinking, then make sure the path can shrink that far (#593) + if (_groupDelta < 0 && + Math.Min(pathBounds.Width, pathBounds.Height) < -_groupDelta * 2) + continue; + + 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); } - _solution.AddRange(group.outPaths); - group.outPaths.Clear(); } } diff --git a/Delphi/Clipper2Lib/Clipper.Core.pas b/Delphi/Clipper2Lib/Clipper.Core.pas index 737cb383..1ed1e243 100644 --- a/Delphi/Clipper2Lib/Clipper.Core.pas +++ b/Delphi/Clipper2Lib/Clipper.Core.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 24 October 2023 * +* Date : 5 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library module * @@ -64,6 +64,7 @@ TPointD = record function GetWidth: Int64; {$IFDEF INLINING} inline; {$ENDIF} function GetHeight: Int64; {$IFDEF INLINING} inline; {$ENDIF} function GetIsEmpty: Boolean; {$IFDEF INLINING} inline; {$ENDIF} + function GetIsValid: Boolean; {$IFDEF INLINING} inline; {$ENDIF} function GetMidPoint: TPoint64; {$IFDEF INLINING} inline; {$ENDIF} public Left : Int64; @@ -78,6 +79,7 @@ TPointD = record property Width: Int64 read GetWidth; property Height: Int64 read GetHeight; property IsEmpty: Boolean read GetIsEmpty; + property IsValid: Boolean read GetIsValid; property MidPoint: TPoint64 read GetMidPoint; end; @@ -86,6 +88,7 @@ TPointD = record function GetWidth: double; {$IFDEF INLINING} inline; {$ENDIF} function GetHeight: double; {$IFDEF INLINING} inline; {$ENDIF} function GetIsEmpty: Boolean; {$IFDEF INLINING} inline; {$ENDIF} + function GetIsValid: Boolean; {$IFDEF INLINING} inline; {$ENDIF} function GetMidPoint: TPointD; {$IFDEF INLINING} inline; {$ENDIF} public Left : double; @@ -99,6 +102,7 @@ TPointD = record property Width: double read GetWidth; property Height: double read GetHeight; property IsEmpty: Boolean read GetIsEmpty; + property IsValid: Boolean read GetIsValid; property MidPoint: TPointD read GetMidPoint; end; @@ -335,6 +339,7 @@ procedure CheckPrecisionRange(var precision: integer); const MaxInt64 = 9223372036854775807; + MinInt64 = -MaxInt64; MaxCoord = MaxInt64 div 4; MinCoord = - MaxCoord; invalid64 = MaxInt64; @@ -346,6 +351,11 @@ procedure CheckPrecisionRange(var precision: integer); InvalidPtD : TPointD = (X: invalidD; Y: invalidD); NullRectD : TRectD = (left: 0; top: 0; right: 0; Bottom: 0); + InvalidRect64 : TRect64 = + (left: invalid64; top: invalid64; right: invalid64; bottom: invalid64); + InvalidRectD : TRectD = + (left: invalidD; top: invalidD; right: invalidD; bottom: invalidD); + Tolerance : Double = 1.0E-12; //https://github.com/AngusJohnson/Clipper2/discussions/564 @@ -378,6 +388,12 @@ function TRect64.GetIsEmpty: Boolean; end; //------------------------------------------------------------------------------ +function TRect64.GetIsValid: Boolean; +begin + result := left <> invalid64; +end; +//------------------------------------------------------------------------------ + function TRect64.GetMidPoint: TPoint64; begin result := Point64((Left + Right) div 2, (Top + Bottom) div 2); @@ -450,6 +466,12 @@ function TRectD.GetIsEmpty: Boolean; end; //------------------------------------------------------------------------------ +function TRectD.GetIsValid: Boolean; +begin + result := left <> invalidD; +end; +//------------------------------------------------------------------------------ + function TRectD.GetMidPoint: TPointD; begin result := PointD((Left + Right) *0.5, (Top + Bottom) *0.5); diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index f3bf669a..1ec5f664 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 24 September 2023 * +* Date : 8 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -32,13 +32,16 @@ interface TDeltaCallback64 = function (const path: TPath64; const path_norms: TPathD; currIdx, prevIdx: integer): double of object; + TRect64Array = array of TRect64; TGroup = class paths : TPaths64; - reversed : Boolean; joinType : TJoinType; endType : TEndType; - constructor Create(jt: TJoinType; et: TEndType); + reversed : Boolean; + lowestPathIdx: integer; + boundsList: TRect64Array; + constructor Create(const pathsIn: TPaths64; jt: TJoinType; et: TEndType); end; TClipperOffset = class @@ -58,9 +61,10 @@ TClipperOffset = class fGroupList : TListEx; fInPath : TPath64; fOutPath : TPath64; - fOutPaths : TPaths64; fOutPathLen : Integer; fSolution : TPaths64; + fSolutionLen : Integer; + fSolutionTree : TPolyTree64; fPreserveCollinear : Boolean; fReverseSolution : Boolean; fDeltaCallback64 : TDeltaCallback64; @@ -83,6 +87,10 @@ TClipperOffset = class procedure OffsetPolygon; procedure OffsetOpenJoined; procedure OffsetOpenPath; + function CalcSolutionCapacity: integer; + procedure UpdateSolution; {$IFDEF INLINING} inline; {$ENDIF} + + function CheckReverseOrientation: Boolean; procedure ExecuteInternal(delta: Double); public constructor Create(miterLimit: double = 2.0; @@ -119,6 +127,10 @@ implementation uses Math; +resourcestring + rsClipper_CoordRangeError = + 'Offsetting will exceed the valid coordinate range'; + const TwoPi : Double = 2 * PI; InvTwoPi : Double = 1/(2 * PI); @@ -127,6 +139,83 @@ implementation // Miscellaneous offset support functions //------------------------------------------------------------------------------ +function GetMultiBounds(const paths: TPaths64; endType: TEndType): TRect64Array; +var + i,j, len, len2, minPathLen: integer; + path: TPath64; + pt1, pt: TPoint64; + r: TRect64; +begin + if endType = etPolygon then + minPathLen := 3 else + minPathLen := 1; + len := Length(paths); + SetLength(Result, len); + for i := 0 to len -1 do + begin + path := paths[i]; + len2 := Length(path); + if len2 < minPathLen then + begin + Result[i] := InvalidRect64; + continue; + end; + pt1 := path[0]; + r := Rect64(pt1.X, pt1.Y, pt1.X, pt1.Y); + for j := 1 to len2 -1 do + begin + pt := path[j]; + if (pt.y > r.bottom) then r.bottom := pt.y + else if (pt.y < r.top) then r.top := pt.y; + if (pt.x > r.right) then r.right := pt.x + else if (pt.x < r.left) then r.left := pt.x; + end; + Result[i] := r; + end; +end; +//------------------------------------------------------------------------------ + +function ValidateBounds(const boundsList: TRect64Array; delta: double): Boolean; +var + i: integer; + iDelta, big, small: Int64; +begin + Result := false; + iDelta := Round(delta); + big := MaxCoord - iDelta; + small := MinCoord + iDelta; + for i := 0 to High(boundsList) do + with boundsList[i] do + begin + if not IsValid then continue; // skip invalid paths + if (left < small) or (right > big) or + (top < small) or (bottom > big) then Exit; + end; + Result := true; +end; +//------------------------------------------------------------------------------ + +function GetLowestClosedPathIdx(const boundsList: TRect64Array): integer; +var + i: integer; + botPt: TPoint64; +begin + Result := -1; + botPt := Point64(MaxInt64, MinInt64); + for i := 0 to High(boundsList) do + with boundsList[i] do + begin + if not IsValid or IsEmpty then Continue; + if (bottom > botPt.y) or + ((bottom = botPt.Y) and (left < botPt.X)) then + begin + botPt := Point64(left, bottom); + Result := i; + end; + end; +end; +//------------------------------------------------------------------------------ + function DotProduct(const vec1, vec2: TPointD): double; {$IFDEF INLINING} inline; {$ENDIF} begin @@ -215,10 +304,31 @@ function UnsafeGet(List: TList; Index: Integer): Pointer; // TGroup methods //------------------------------------------------------------------------------ -constructor TGroup.Create(jt: TJoinType; et: TEndType); +constructor TGroup.Create(const pathsIn: TPaths64; jt: TJoinType; et: TEndType); +var + i, len: integer; + isJoined: boolean; begin Self.joinType := jt; Self.endType := et; + + isJoined := et in [etPolygon, etJoined]; + len := Length(pathsIn); + SetLength(paths, len); + for i := 0 to len -1 do + paths[i] := StripDuplicates(pathsIn[i], isJoined); + + boundsList := GetMultiBounds(paths, et); + if (et = etPolygon) then + begin + lowestPathIdx := GetLowestClosedPathIdx(boundsList); + reversed := (lowestPathIdx >= 0) and ( + Area(pathsIn[lowestPathIdx]) < 0); + end else + begin + lowestPathIdx := -1; + reversed := false; + end; end; //------------------------------------------------------------------------------ @@ -253,6 +363,7 @@ procedure TClipperOffset.Clear; TGroup(fGroupList[i]).Free; fGroupList.Clear; fSolution := nil; + fSolutionLen := 0; end; //------------------------------------------------------------------------------ @@ -274,8 +385,7 @@ procedure TClipperOffset.AddPaths(const paths: TPaths64; group: TGroup; begin if Length(paths) = 0 then Exit; - group := TGroup.Create(joinType, endType); - AppendPaths(group.paths, paths); + group := TGroup.Create(paths, joinType, endType); fGroupList.Add(group); end; //------------------------------------------------------------------------------ @@ -302,37 +412,35 @@ function GetPerpendicD(const pt: TPoint64; const norm: TPointD; delta: double): procedure TClipperOffset.DoGroupOffset(group: TGroup); var - i,j, len, lowestIdx, steps: Integer; - r, stepsPer360, arcTol, area: Double; + i,j, len, steps: Integer; + r, stepsPer360, arcTol: Double; absDelta: double; rec: TRect64; - isJoined: Boolean; + pt0: TPoint64; begin + if group.endType = etPolygon then begin - // the lowermost polygon must be an outer polygon. So we can use that as the - // designated orientation for outer polygons (needed for tidy-up clipping) - lowestIdx := GetLowestPolygonIdx(group.paths); - if lowestIdx < 0 then Exit; - // nb: don't use the default orientation here ... - area := Clipper.Core.Area(group.paths[lowestIdx]); - //if area = 0 then Exit; // this is probably unhelpful (#430) - group.reversed := (area < 0); - if group.reversed then fGroupDelta := -fDelta - else fGroupDelta := fDelta; + if (group.lowestPathIdx < 0) then Exit; + //if (area == 0) return; // probably unhelpful (#430) + if group.reversed then + fGroupDelta := -fDelta else + fGroupDelta := fDelta; end else begin - group.reversed := false; fGroupDelta := Abs(fDelta) * 0.5; end; + + absDelta := Abs(fGroupDelta); + if not ValidateBounds(group.boundsList, absDelta) then + Raise EClipper2LibException(rsClipper_CoordRangeError); + fJoinType := group.joinType; fEndType := group.endType; // calculate a sensible number of steps (for 360 deg for the given offset - if (not Assigned(fDeltaCallback64) and - (group.joinType = jtRound) or (group.endType = etRound)) then + if (group.joinType = jtRound) or (group.endType = etRound) then begin - absDelta := Abs(fGroupDelta); // arcTol - when fArcTolerance is undefined (0), the amount of // curve imprecision that's allowed is based on the size of the // offset (delta). Obviously very large offsets will almost always @@ -350,72 +458,60 @@ procedure TClipperOffset.DoGroupOffset(group: TGroup); fStepsPerRad := stepsPer360 / TwoPi; end; - fOutPaths := nil; - isJoined := fEndType in [etPolygon, etJoined]; for i := 0 to High(group.paths) do begin - fInPath := StripDuplicates(group.paths[i], IsJoined); - len := Length(fInPath); - if (len = 0) or ((len < 3) and (fEndType = etPolygon)) then - Continue; + if not group.boundsList[i].IsValid then Continue; + fInPath := group.paths[i]; fNorms := nil; - fOutPath := nil; - fOutPathLen := 0; //if a single vertex then build a circle or a square ... + len := Length(fInPath); if len = 1 then begin if fGroupDelta < 1 then Continue; - absDelta := Abs(fGroupDelta); + pt0 := fInPath[0]; if (group.endType = etRound) then begin r := absDelta; - with fInPath[0] do - begin - steps := Ceil(fStepsPerRad * TwoPi); //#617 - fOutPath := Path64(Ellipse(RectD(X-r, Y-r, X+r, Y+r), steps)); + steps := Ceil(fStepsPerRad * TwoPi); //#617 + fOutPath := Path64(Ellipse( + RectD(pt0.X-r, pt0.Y-r, pt0.X+r, pt0.Y+r), steps)); {$IFDEF USINGZ} - for j := 0 to high(fOutPath) do - fOutPath[j].Z := Z; + for j := 0 to high(fOutPath) do + fOutPath[j].Z := pt0.Z; {$ENDIF} - end; end else begin j := Round(absDelta); - with fInPath[0] do - begin - rec := Rect64(X -j, Y -j, X+j, Y+j); - fOutPath := rec.AsPath; + rec := Rect64(pt0.X -j, pt0.Y -j, pt0.X+j, pt0.Y+j); + fOutPath := rec.AsPath; {$IFDEF USINGZ} - for j := 0 to high(fOutPath) do - fOutPath[j].Z := Z; + for j := 0 to high(fOutPath) do + fOutPath[j].Z := pt0.Z; {$ENDIF} - end end; - AppendPath(fOutPaths, fOutPath); + UpdateSolution; Continue; - end else - begin - if (len = 2) and (group.endType = etJoined) then - begin - if fJoinType = jtRound then - fEndType := etRound else - fEndType := etSquare; - end; - BuildNormals; + end; + + // when shrinking, then make sure the path can shrink that far + if (fGroupDelta < 0) and + (Min(group.boundsList[i].Width, group.boundsList[i].Height) < + fGroupDelta *2) then Continue; - if fEndType = etPolygon then OffsetPolygon - else if fEndType = etJoined then OffsetOpenJoined - else OffsetOpenPath; + if (len = 2) and (group.endType = etJoined) then + begin + if fJoinType = jtRound then + fEndType := etRound else + fEndType := etSquare; end; - if fOutPathLen = 0 then Continue; - SetLength(fOutPath, fOutPathLen); - AppendPath(fOutPaths, fOutPath); + BuildNormals; + if fEndType = etPolygon then OffsetPolygon + else if fEndType = etJoined then OffsetOpenJoined + else OffsetOpenPath; end; - // finally copy the working 'outPaths' to the solution - AppendPaths(fSolution, fOutPaths); end; //------------------------------------------------------------------------------ @@ -431,38 +527,45 @@ procedure TClipperOffset.BuildNormals; end; //------------------------------------------------------------------------------ +procedure TClipperOffset.UpdateSolution; +begin + if fOutPathLen = 0 then Exit; + SetLength(fOutPath, fOutPathLen); + fSolution[fSolutionLen] := fOutPath; + inc(fSolutionLen); + fOutPath := nil; + fOutPathLen := 0; +end; +//------------------------------------------------------------------------------ + +function TClipperOffset.CalcSolutionCapacity: integer; +var + i: integer; +begin + Result := 0; + for i := 0 to fGroupList.Count -1 do + with TGroup(fGroupList[i]) do + if endType = etJoined then + inc(Result, Length(paths) *2) else + inc(Result, Length(paths)); +end; +//------------------------------------------------------------------------------ + procedure TClipperOffset.OffsetPolygon; var i,j: integer; - a, offsetMinDim: double; - rec: TRect64; begin - //when the path is contracting, make sure - //there is sufficient space to do so. //#593 - //nb: this will have a small impact on performance - a := Area(fInPath); - if (a < 0) <> (fGroupDelta < 0) then - begin - rec := GetBounds(fInPath); - offsetMinDim := Abs(fGroupDelta) * 2; - if (offsetMinDim >= rec.Width) or (offsetMinDim >= rec.Height) then Exit; - end; - j := high(fInPath); for i := 0 to high(fInPath) do OffsetPoint(i, j); + UpdateSolution; end; //------------------------------------------------------------------------------ procedure TClipperOffset.OffsetOpenJoined; begin OffsetPolygon; - SetLength(fOutPath, fOutPathLen); - AppendPath(fOutPaths, fOutPath); - fOutPath := nil; - fOutPathLen := 0; fInPath := ReversePath(fInPath); - // Rebuild normals // BuildNormals; fNorms := ReversePath(fNorms); fNorms := ShiftPath(fNorms, 1); @@ -523,16 +626,23 @@ procedure TClipperOffset.OffsetOpenPath; k := 0; for i := highI downto 1 do //and stop at 1! OffsetPoint(i, k); + + UpdateSolution; end; //------------------------------------------------------------------------------ procedure TClipperOffset.ExecuteInternal(delta: Double); var - i: integer; + i,j: integer; group: TGroup; + pathsReversed: Boolean; + fillRule: TFillRule; + dummy: TPaths64; begin fSolution := nil; + fSolutionLen := 0; if fGroupList.Count = 0 then Exit; + SetLength(fSolution, CalcSolutionCapacity); fMinLenSqrd := 1; if abs(delta) < Tolerance then @@ -541,7 +651,11 @@ procedure TClipperOffset.ExecuteInternal(delta: Double); for i := 0 to fGroupList.Count -1 do begin group := TGroup(fGroupList[i]); - AppendPaths(fSolution, group.paths); + for j := 0 to High(group.paths) do + begin + fSolution[fSolutionLen] := group.paths[i]; + inc(fSolutionLen); + end; end; Exit; end; @@ -558,45 +672,52 @@ procedure TClipperOffset.ExecuteInternal(delta: Double); group := TGroup(fGroupList[i]); DoGroupOffset(group); end; + SetLength(fSolution, fSolutionLen); + + pathsReversed := CheckReverseOrientation(); + if pathsReversed then + fillRule := frNegative else + fillRule := frPositive; // clean up self-intersections ... with TClipper64.Create do try PreserveCollinear := fPreserveCollinear; // the solution should retain the orientation of the input - ReverseSolution := - fReverseSolution <> TGroup(fGroupList[0]).reversed; + ReverseSolution := fReverseSolution <> pathsReversed; AddSubject(fSolution); - if TGroup(fGroupList[0]).reversed then - Execute(ctUnion, frNegative, fSolution) else - Execute(ctUnion, frPositive, fSolution); + if assigned(fSolutionTree) then + Execute(ctUnion, fillRule, fSolutionTree, dummy); + Execute(ctUnion, fillRule, fSolution); finally free; end; end; //------------------------------------------------------------------------------ +function TClipperOffset.CheckReverseOrientation: Boolean; +var + i: integer; +begin + Result := false; + // find the orientation of the first closed path + for i := 0 to fGroupList.Count -1 do + with TGroup(fGroupList[i]) do + if endType = etPolygon then + begin + Result := reversed; + break; + end; +end; +//------------------------------------------------------------------------------ + procedure TClipperOffset.Execute(delta: Double; out solution: TPaths64); begin - fSolution := nil; solution := nil; - ExecuteInternal(delta); + fSolutionTree := nil; if fGroupList.Count = 0 then Exit; - - // clean up self-intersections ... - with TClipper64.Create do - try - PreserveCollinear := fPreserveCollinear; - // the solution should retain the orientation of the input - ReverseSolution := - fReverseSolution <> TGroup(fGroupList[0]).reversed; - AddSubject(fSolution); - if TGroup(fGroupList[0]).reversed then - Execute(ctUnion, frNegative, solution) else - Execute(ctUnion, frPositive, solution); - finally - free; - end; + ExecuteInternal(delta); + solution := fSolution; end; //------------------------------------------------------------------------------ @@ -608,29 +729,12 @@ procedure TClipperOffset.Execute(DeltaCallback: TDeltaCallback64; out solution: //------------------------------------------------------------------------------ procedure TClipperOffset.Execute(delta: Double; polytree: TPolyTree64); -var - dummy: TPaths64; begin - fSolution := nil; if not Assigned(polytree) then Raise EClipper2LibException(rsClipper_PolyTreeErr); - + fSolutionTree := polytree; + fSolutionTree.Clear; ExecuteInternal(delta); - - // clean up self-intersections ... - with TClipper64.Create do - try - PreserveCollinear := fPreserveCollinear; - // the solution should retain the orientation of the input - ReverseSolution := - fReverseSolution <> TGroup(fGroupList[0]).reversed; - AddSubject(fSolution); - if TGroup(fGroupList[0]).reversed then - Execute(ctUnion, frNegative, polytree, dummy) else - Execute(ctUnion, frPositive, polytree, dummy); - finally - free; - end; end; //------------------------------------------------------------------------------ diff --git a/Delphi/Clipper2Lib/Clipper.inc b/Delphi/Clipper2Lib/Clipper.inc index 066d5dc7..5b15f920 100644 --- a/Delphi/Clipper2Lib/Clipper.inc +++ b/Delphi/Clipper2Lib/Clipper.inc @@ -14,21 +14,21 @@ {$DEFINE INLINING} {$MODE DELPHI} {$ELSE} - {$IF CompilerVersion < 14} + {$IF COMPILERVERSION < 14} Requires Delphi version 6 or above. {$IFEND} - {$IF CompilerVersion >= 18} //Delphi 2007 + {$IF COMPILERVERSION >= 18} //Delphi 2007 {$DEFINE RECORD_METHODS} {$DEFINE STRICT} - {$IF CompilerVersion >= 19} //Delphi 2009 + {$IF COMPILERVERSION >= 19} //Delphi 2009 //While "inlining" is supported from D2005, it's buggy (see QC41166) until D2009 {$DEFINE INLINING} - {$IFEND} - {$IF COMPILERVERSION >= 23} //Delphi XE2+ - {$DEFINE XPLAT_GENERICS} - {$DEFINE ROUNDINGMODE} - {$IF COMPILERVERSION >= 24} //Delphi XE3+ - {$LEGACYIFEND ON} + {$IF COMPILERVERSION >= 23} //Delphi XE2+ + {$DEFINE XPLAT_GENERICS} + {$DEFINE ROUNDINGMODE} + {$IF COMPILERVERSION >= 24} //Delphi XE3+ + {$LEGACYIFEND ON} + {$IFEND} {$IFEND} {$IFEND} {$IFEND} From 2c4626badde4aa8c703a8b0e2b00dd773c72840c Mon Sep 17 00:00:00 2001 From: angusj Date: Thu, 9 Nov 2023 14:35:33 +1000 Subject: [PATCH 74/95] Correct of typo in code documentation :) --- CPP/Tests/TestOffsetOrientation.cpp | 8 +++++--- CPP/Tests/googletest | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) delete mode 160000 CPP/Tests/googletest diff --git a/CPP/Tests/TestOffsetOrientation.cpp b/CPP/Tests/TestOffsetOrientation.cpp index 8ebfc533..b668a097 100644 --- a/CPP/Tests/TestOffsetOrientation.cpp +++ b/CPP/Tests/TestOffsetOrientation.cpp @@ -24,9 +24,11 @@ TEST(Clipper2Tests, TestOffsettingOrientation2) { co.Execute(5, solution); ASSERT_EQ(solution.size(), 2); - // when offsetting, output orientation should match input EXCEPT when ReverseSolution == true - // HOWEVER, input path order MANY NOT MATCH output path order, for example when inner paths (holes) - // are defined before their container outer paths (as above). + // When offsetting, output orientation should match input EXCEPT when ReverseSolution == true + // However, input path ORDER may not match output path order. For example, order will change + // whenever inner paths (holes) are defined before their container outer paths (as above). + // And when offsetting multiple outer paths, their order will likely change too. Due to the + // sweep-line algorithm used, paths with larger Y coordinates will likely be listed first. EXPECT_TRUE(Clipper2Lib::IsPositive(subject[1]) != Clipper2Lib::IsPositive(solution[0])); } diff --git a/CPP/Tests/googletest b/CPP/Tests/googletest deleted file mode 160000 index 5e6a5336..00000000 --- a/CPP/Tests/googletest +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5e6a533680fc8292c31f31664d80c48440d4a526 From 36cf9f7ff3b05d97183bb93d04c57c65e5de4d0c Mon Sep 17 00:00:00 2001 From: angusj Date: Thu, 9 Nov 2023 15:35:02 +1000 Subject: [PATCH 75/95] Revert "Correct of typo in code documentation :)" This reverts commit 2c4626badde4aa8c703a8b0e2b00dd773c72840c. --- CPP/Tests/TestOffsetOrientation.cpp | 8 +++----- CPP/Tests/googletest | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) create mode 160000 CPP/Tests/googletest diff --git a/CPP/Tests/TestOffsetOrientation.cpp b/CPP/Tests/TestOffsetOrientation.cpp index b668a097..8ebfc533 100644 --- a/CPP/Tests/TestOffsetOrientation.cpp +++ b/CPP/Tests/TestOffsetOrientation.cpp @@ -24,11 +24,9 @@ TEST(Clipper2Tests, TestOffsettingOrientation2) { co.Execute(5, solution); ASSERT_EQ(solution.size(), 2); - // When offsetting, output orientation should match input EXCEPT when ReverseSolution == true - // However, input path ORDER may not match output path order. For example, order will change - // whenever inner paths (holes) are defined before their container outer paths (as above). - // And when offsetting multiple outer paths, their order will likely change too. Due to the - // sweep-line algorithm used, paths with larger Y coordinates will likely be listed first. + // when offsetting, output orientation should match input EXCEPT when ReverseSolution == true + // HOWEVER, input path order MANY NOT MATCH output path order, for example when inner paths (holes) + // are defined before their container outer paths (as above). EXPECT_TRUE(Clipper2Lib::IsPositive(subject[1]) != Clipper2Lib::IsPositive(solution[0])); } diff --git a/CPP/Tests/googletest b/CPP/Tests/googletest new file mode 160000 index 00000000..5e6a5336 --- /dev/null +++ b/CPP/Tests/googletest @@ -0,0 +1 @@ +Subproject commit 5e6a533680fc8292c31f31664d80c48440d4a526 From 571cab31d91273dc3be429c7052d99cbd4194705 Mon Sep 17 00:00:00 2001 From: angusj Date: Thu, 9 Nov 2023 15:41:27 +1000 Subject: [PATCH 76/95] Corrected a typo in code documentation --- CPP/Tests/TestOffsetOrientation.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CPP/Tests/TestOffsetOrientation.cpp b/CPP/Tests/TestOffsetOrientation.cpp index 8ebfc533..b668a097 100644 --- a/CPP/Tests/TestOffsetOrientation.cpp +++ b/CPP/Tests/TestOffsetOrientation.cpp @@ -24,9 +24,11 @@ TEST(Clipper2Tests, TestOffsettingOrientation2) { co.Execute(5, solution); ASSERT_EQ(solution.size(), 2); - // when offsetting, output orientation should match input EXCEPT when ReverseSolution == true - // HOWEVER, input path order MANY NOT MATCH output path order, for example when inner paths (holes) - // are defined before their container outer paths (as above). + // When offsetting, output orientation should match input EXCEPT when ReverseSolution == true + // However, input path ORDER may not match output path order. For example, order will change + // whenever inner paths (holes) are defined before their container outer paths (as above). + // And when offsetting multiple outer paths, their order will likely change too. Due to the + // sweep-line algorithm used, paths with larger Y coordinates will likely be listed first. EXPECT_TRUE(Clipper2Lib::IsPositive(subject[1]) != Clipper2Lib::IsPositive(solution[0])); } From 4c43946874bb531d05c96782cf9bf3c04de7ed11 Mon Sep 17 00:00:00 2001 From: Angus Johnson Date: Tue, 14 Nov 2023 19:06:01 +1000 Subject: [PATCH 77/95] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 472daf25..b6857a31 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ 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. ### Compilers +Clipper2 can be compiled using either C++, or C#, or Delphi Pascal. Nevertheless, the library's performance does [differ](https://www.angusj.com/clipper2/Docs/Changes.htm) between these languages, with the C++ compiled code being noticeably faster. The library can be accessed using many other programming languages too, simply by dynamically linking to various c++ compiled and exported library functions (in a DLL file). C# and Delphi users may well do this too when performance is critical. | Lang. | Requirements | | --- | --- | | [**C++:**](https://github.com/AngusJohnson/Clipper2/tree/main/CPP) | Requires C++17 (could be modified to C++11 with relatively minor changes), **or**| From 249c4406de864c3de30f5830131a62866d144df4 Mon Sep 17 00:00:00 2001 From: Angus Johnson Date: Tue, 14 Nov 2023 19:18:42 +1000 Subject: [PATCH 78/95] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6857a31..26c5e2c3 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ 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. ### Compilers -Clipper2 can be compiled using either C++, or C#, or Delphi Pascal. Nevertheless, the library's performance does [differ](https://www.angusj.com/clipper2/Docs/Changes.htm) between these languages, with the C++ compiled code being noticeably faster. The library can be accessed using many other programming languages too, simply by dynamically linking to various c++ compiled and exported library functions (in a DLL file). C# and Delphi users may well do this too when performance is critical. +Clipper2 can be compiled using either C++, or C#, or Delphi Pascal. Nevertheless, the library's performance does [differ](https://www.angusj.com/clipper2/Docs/Changes.htm) between these languages, with the C++ compiled code being noticeably faster. The library can be accessed using many other programming languages too, simply by dynamically linking to various C++ compiled and exported library functions (in a [DLL](https://github.com/AngusJohnson/Clipper2/tree/main/DLL) file). C# and Delphi users may well do this too when performance is critical. | Lang. | Requirements | | --- | --- | | [**C++:**](https://github.com/AngusJohnson/Clipper2/tree/main/CPP) | Requires C++17 (could be modified to C++11 with relatively minor changes), **or**| From e600a239c7bb722336015cbcb9dcb80eaf0cbd28 Mon Sep 17 00:00:00 2001 From: Angus Johnson Date: Wed, 15 Nov 2023 06:36:19 +1000 Subject: [PATCH 79/95] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 26c5e2c3..42c23922 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ 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. ### Compilers -Clipper2 can be compiled using either C++, or C#, or Delphi Pascal. Nevertheless, the library's performance does [differ](https://www.angusj.com/clipper2/Docs/Changes.htm) between these languages, with the C++ compiled code being noticeably faster. The library can be accessed using many other programming languages too, simply by dynamically linking to various C++ compiled and exported library functions (in a [DLL](https://github.com/AngusJohnson/Clipper2/tree/main/DLL) file). C# and Delphi users may well do this too when performance is critical. +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.) | Lang. | Requirements | | --- | --- | | [**C++:**](https://github.com/AngusJohnson/Clipper2/tree/main/CPP) | Requires C++17 (could be modified to C++11 with relatively minor changes), **or**| From 21e3db57193bc6613f1eee4a23d9ee8cff19f0cd Mon Sep 17 00:00:00 2001 From: Angus Johnson Date: Wed, 15 Nov 2023 06:41:21 +1000 Subject: [PATCH 80/95] Update ReadMe.md --- DLL/ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DLL/ReadMe.md b/DLL/ReadMe.md index f075f736..b280058a 100644 --- a/DLL/ReadMe.md +++ b/DLL/ReadMe.md @@ -1,6 +1,6 @@ # Clipper2 DLL -The Clipper2 library is written in several languages (C++, C# & Delphi Pascal) and can be compiled and linked directly into applications using any one of those languages. Now developers who use other programming languages can also access almost all of Clipper2's features using a C++ compiled dynamically linked library. And because the C++ code is significantly faster than either C# or Delphi, developers using those languages may also prefer using the DLL when performance is critical. +The Clipper2 library is written in several languages (C++, C# & Delphi Pascal) and can be compiled and linked directly into applications using any one of those languages. Now developers who use other programming languages can also access almost all of Clipper2's features using a C++ compiled dynamically linked library. And because the C++ code is significantly faster than either C# or Delphi, developers using those languages may also prefer using the DLL when performance is critical. (The latest precompiled DLLs can be found in [Clipper2's Releases](https://github.com/AngusJohnson/Clipper2/releases)). This folder contains: 1. C++ code that compiles **Clipper2** into Windows DLLs (both 32bit and 64bit). From ab39a5ef6edee82775a81ea0f75edb96fa837805 Mon Sep 17 00:00:00 2001 From: Angus Johnson Date: Wed, 15 Nov 2023 06:47:17 +1000 Subject: [PATCH 81/95] Update ReadMe.md --- DLL/ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DLL/ReadMe.md b/DLL/ReadMe.md index b280058a..0e7a1b61 100644 --- a/DLL/ReadMe.md +++ b/DLL/ReadMe.md @@ -1,6 +1,6 @@ # Clipper2 DLL -The Clipper2 library is written in several languages (C++, C# & Delphi Pascal) and can be compiled and linked directly into applications using any one of those languages. Now developers who use other programming languages can also access almost all of Clipper2's features using a C++ compiled dynamically linked library. And because the C++ code is significantly faster than either C# or Delphi, developers using those languages may also prefer using the DLL when performance is critical. (The latest precompiled DLLs can be found in [Clipper2's Releases](https://github.com/AngusJohnson/Clipper2/releases)). +The Clipper2 library is written in several languages (C++, C# & Delphi Pascal) and can be compiled and statically linked into applications using those languages. Now developers using other programming languages can also access almost all of Clipper2's features by dynamically linked to a C++ compiled library. (The latest precompiled DLLs can be found in [Clipper2's Releases](https://github.com/AngusJohnson/Clipper2/releases)). This folder contains: 1. C++ code that compiles **Clipper2** into Windows DLLs (both 32bit and 64bit). From 028a2b087b204459fe85f9f550470e27f9907974 Mon Sep 17 00:00:00 2001 From: Angus Johnson Date: Wed, 15 Nov 2023 06:48:28 +1000 Subject: [PATCH 82/95] Update ReadMe.md --- DLL/ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DLL/ReadMe.md b/DLL/ReadMe.md index 0e7a1b61..2f39f6db 100644 --- a/DLL/ReadMe.md +++ b/DLL/ReadMe.md @@ -1,6 +1,6 @@ # Clipper2 DLL -The Clipper2 library is written in several languages (C++, C# & Delphi Pascal) and can be compiled and statically linked into applications using those languages. Now developers using other programming languages can also access almost all of Clipper2's features by dynamically linked to a C++ compiled library. (The latest precompiled DLLs can be found in [Clipper2's Releases](https://github.com/AngusJohnson/Clipper2/releases)). +The Clipper2 library is written in several languages (C++, C# & Delphi Pascal) and can be compiled and statically linked into applications using those languages. Now developers using other programming languages can also access almost all of Clipper2's features by dynamically linking to a C++ compiled library. (The latest precompiled DLLs can be found in [Clipper2's Releases](https://github.com/AngusJohnson/Clipper2/releases)). This folder contains: 1. C++ code that compiles **Clipper2** into Windows DLLs (both 32bit and 64bit). From c3839acbc07ecdfa6a361e2608c897fd4e4f737c Mon Sep 17 00:00:00 2001 From: angusj Date: Wed, 15 Nov 2023 16:38:58 +1000 Subject: [PATCH 83/95] Offsetting open paths has changed delta (#707) Minor changes to clipper.export.h Renamed DisposeExportedCPaths64 to DisposeArray64 Renamed DisposeExportedCPathsD to DisposeArrayD Tidied up code documentation --- .../include/clipper2/clipper.export.h | 133 +++++++-------- CPP/Clipper2Lib/include/clipper2/clipper.h | 37 ++--- CPP/Clipper2Lib/src/clipper.offset.cpp | 6 +- CPP/Tests/TestExportHeaders.cpp | 16 +- CSharp/Clipper2Lib/Clipper.Offset.cs | 4 +- DLL/CSharp_TestApp/Program.cs | 41 ++--- DLL/CSharp_TestApp2/Program.cs | 38 ++--- DLL/Delphi_TestApp/Test_DLL.dpr | 154 +++++++----------- Delphi/Clipper2Lib/Clipper.Offset.pas | 4 +- 9 files changed, 201 insertions(+), 232 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index 8e0c7280..7e789e19 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -1,53 +1,76 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 1 November 2023 * +* Date : 15 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module exports the Clipper2 Library (ie DLL/so) * * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ -// ============================================================================= -// CAUTION: THE FOLLOWING DATA STRUCTURES HAVE RECENTLY BEEN CHANGED (ver 1.2.4) -// ============================================================================= -// -// The path structures that are used extensively in other parts of this library -// are all based on std::vector classes. And unfortunately, C++ classes can't -// be accessed by other languages. So all these (std::vector based) paths must -// be converted into simple C data structures that can be understood by just -// about any programming language. And these C style data structures are mostly -// just simple arrays. -// -// Path64 and PathD are converted into arrays of int64_t or double values -// respectively, representing consecutive x and y coordinates. -// However preceeding each array is single x,y pair that contains the -// path's length in the x value (and the 'y' value = 0). -// __________________________________ -// |counter|coord1|coord2|...|coordN| -// |N, 0 |x1, y1|x2, y2|...|xN, yN| -// __________________________________ -// -// CPaths64 and CPathsD: -// These structures are very similar to their respecitve path structures. These -// structures not only contain any number of consecutive CPath64 or CPathD -// structures but, preceeding these paths, there is an extra x,y pair of values -// that contains the path count. However, in this case the x value = array length, -// and the y value contains the count (ie the number of following paths). -// _______________________________ -// |counter|path1|path2|...|pathN| -// |len, N | | |...|pathN| -// _______________________________ -// -// -// CPolytree64 and CPolytreeD: -// These are both simple arrays (of int64_t or double respectively). Otherwise -// their structures are identical, and consist of series of CPolyPath structures. -// The CPolyPath structure is as follows: -// Usually polygon length (N) except for the very first CPolyPath entry that -// contains the array size. (The top-most CPolyPath never contains a polygon.) -// ChildCount (C) -// N * x,y coordinates in Polygon -// C * Nested child CPolyPath structures + +/* + Boolean clipping: + cliptype: None=0, Intersection=1, Union=2, Difference=3, Xor=4 + fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3 + + Polygon offsetting (inflate/deflate): + jointype: Square=0, Bevel=1, Round=2, Miter=3 + endtype: Polygon=0, Joined=1, Butt=2, Square=3, Round=4 + +The path structures used extensively in other parts of this library are all +based on std::vector classes. Since C++ classes can't be accessed by other +languages, these paths must be converted into simple C data structures that +can be understood by just about any programming language. And these C style +path structures will be simple arrays of int94_t (CPath64) or double (CPathD). + +CPath64 and CPathD: +These are simple 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| +__________________________________ + +CPaths64 and CPathsD: +These are also simple 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 (A) and the number of +contained paths (C). +_______________________________ +|counter|path1|path2|...|pathC| +|A , C | | +_______________________________ + +CPolytree64 and CPolytreeD: +These are also simple arrays consisting of CPolyPath structures. +However, since the very first CPolyPath is the tree container, it has no +path and its structure will be very slightly different from the remaining +CPolyPath. This difference will be discussed below. + +CPolyPath64 and CPolyPathD: +These are simple arrays consisting path coordinates followed by any number of +child CPolyPath (representing the paths they own). Preceding these are a pair +of values: the length of its 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 +(both directly and indirectly) for every other CPolyPath in the tree. So +where this first CPolyPath has no path, instead of a path length, its first +value contains the total length of the CPolytree array (A). + +All the exported structures (CPaths64, CPathsD, CPolyTree64 & CPolyTreeD) +are arrays of type int64_t or double. And the first value in these arrays +will always contain the length of that array. + +These array structures will be allocated in heap memory, so eventually this +memory will need to be released. But because the applications dynamically +linking to these functions may use a different memory manager, the only +really safe way to free up this memory is to use the exported +DisposeArray64 and DisposeArrayD functions below. +*/ #ifndef CLIPPER2_EXPORT_H @@ -114,33 +137,16 @@ inline Rect CRectToRect(const CRect& rect) EXTERN_DLL_EXPORT const char* Version(); -// Most of the exported functions below return data in structures that -// has been allocated in heap memory. Eventually this memory will -// need to be released using one of the following 'DisposeExported...' -// functions. (This may be the only safe way to release this memory -// since the executable accessing these exported functions may use -// a different memory manager (MM)., And allocating memory using one -// MM and releasing the same memory in another will cause problems.) -EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& p) -{ - delete[] p; -} -EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& p) +EXTERN_DLL_EXPORT void DisposeArray64(int64_t*& p) { delete[] p; } -EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64& p) -{ - delete[] p; -} -EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD& p) + +EXTERN_DLL_EXPORT void DisposeArrayD(double*& p) { delete[] p; } -// Boolean clipping: -// cliptype: None=0, Intersection=1, Union=2, Difference=3, Xor=4 -// fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3 EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, uint8_t fillrule, const CPaths64 subjects, const CPaths64 subjects_open, const CPaths64 clips, @@ -165,9 +171,6 @@ EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype, CPolyTreeD& solution, CPathsD& solution_open, int precision = 2, bool preserve_collinear = true, bool reverse_solution = false); -// Polygon offsetting (inflate/deflate): -// jointype: Square=0, Round=1, Miter=2 -// endtype: Polygon=0, Joined=1, Butt=2, Square=3, Round=4 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, diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.h b/CPP/Clipper2Lib/include/clipper2/clipper.h index 2481d1fb..a2d9e84b 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.h @@ -341,6 +341,19 @@ namespace Clipper2Lib { details::OutlinePolyPathD(os, *pp.Child(i), i, preamble + " "); } + template + inline constexpr void MakePathGeneric(const T an_array, + size_t array_size, std::vector& result) + { + result.reserve(array_size / 2); + for (size_t i = 0; i < array_size; i +=2) +#ifdef USINGZ + result.push_back( U{ an_array[i], an_array[i +1], 0} ); +#else + result.push_back( U{ an_array[i], an_array[i + 1]} ); +#endif + } + } // end details namespace inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp) @@ -391,22 +404,6 @@ namespace Clipper2Lib { return true; } - namespace details { - - template - inline constexpr void MakePathGeneric(const T list, size_t size, - std::vector& result) - { - for (size_t i = 0; i < size; ++i) -#ifdef USINGZ - result[i / 2] = U{list[i], list[++i], 0}; -#else - result[i / 2] = U{list[i], list[++i]}; -#endif - } - - } // end details namespace - template::value && @@ -417,7 +414,7 @@ namespace Clipper2Lib { const auto size = list.size() - list.size() % 2; if (list.size() != size) DoError(non_pair_error_i); // non-fatal without exception handling - Path64 result(size / 2); // else ignores unpaired value + Path64 result; details::MakePathGeneric(list, size, result); return result; } @@ -431,7 +428,7 @@ namespace Clipper2Lib { { // Make the compiler error on unpaired value (i.e. no runtime effects). static_assert(N % 2 == 0, "MakePath requires an even number of arguments"); - Path64 result(N / 2); + Path64 result; details::MakePathGeneric(list, N, result); return result; } @@ -446,7 +443,7 @@ namespace Clipper2Lib { const auto size = list.size() - list.size() % 2; if (list.size() != size) DoError(non_pair_error_i); // non-fatal without exception handling - PathD result(size / 2); // else ignores unpaired value + PathD result; details::MakePathGeneric(list, size, result); return result; } @@ -460,7 +457,7 @@ namespace Clipper2Lib { { // Make the compiler error on unpaired value (i.e. no runtime effects). static_assert(N % 2 == 0, "MakePath requires an even number of arguments"); - PathD result(N / 2); + PathD result; details::MakePathGeneric(list, N, result); return result; } diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index 9581a643..eaf514cf 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 8 November 2023 * +* Date : 15 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -508,9 +508,9 @@ void ClipperOffset::DoGroupOffset(Group& group) if (group.lowest_path_idx < 0) return; //if (area == 0) return; // probably unhelpful (#430) group_delta_ = (group.is_reversed) ? -delta_ : delta_; - } + } else - group_delta_ = std::abs(delta_) * 0.5; + group_delta_ = std::abs(delta_);// *0.5; double abs_delta = std::fabs(group_delta_); if (!ValidateBounds(group.bounds_list, abs_delta)) diff --git a/CPP/Tests/TestExportHeaders.cpp b/CPP/Tests/TestExportHeaders.cpp index e21a74dc..02ca6655 100644 --- a/CPP/Tests/TestExportHeaders.cpp +++ b/CPP/Tests/TestExportHeaders.cpp @@ -91,8 +91,8 @@ TEST(Clipper2Tests, ExportHeader64) //clean up !!! delete[] c_subj; delete[] c_clip; - DisposeExportedCPaths64(c_sol); - DisposeExportedCPaths64(c_sol_open); + DisposeArray64(c_sol); + DisposeArray64(c_sol_open); EXPECT_EQ(solution.size(), 5); } @@ -125,8 +125,8 @@ TEST(Clipper2Tests, ExportHeaderD) //clean up !!! delete[] c_subj; delete[] c_clip; - DisposeExportedCPathsD(c_sol); - DisposeExportedCPathsD(c_sol_open); + DisposeArrayD(c_sol); + DisposeArrayD(c_sol_open); EXPECT_EQ(solution.size(), 5); } @@ -164,8 +164,8 @@ TEST(Clipper2Tests, ExportHeaderTree64) //clean up !!! delete[] c_subj; delete[] c_clip; - DisposeExportedCPolyTree64(c_sol_tree); - DisposeExportedCPaths64(c_sol_open); + DisposeArray64(c_sol_tree); + DisposeArray64(c_sol_open); PolyPath64* pp = &sol_tree; for (int i = 0; i < 4; ++i) @@ -209,8 +209,8 @@ TEST(Clipper2Tests, ExportHeaderTreeD) //clean up !!! delete[] c_subj; delete[] c_clip; - DisposeExportedCPolyTreeD(c_sol_tree); - DisposeExportedCPathsD(c_sol_open); + DisposeArrayD(c_sol_tree); + DisposeArrayD(c_sol_open); PolyPathD* pp = &sol_tree; for (int i = 0; i < 4; ++i) diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index 32d6961b..cf5479e4 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 8 November 2023 * +* Date : 15 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -677,7 +677,7 @@ private void DoGroupOffset(Group group) _groupDelta = (group.pathsReversed) ? -_delta : _delta; } else - _groupDelta = Math.Abs(_delta) * 0.5; + _groupDelta = Math.Abs(_delta);// * 0.5; double absDelta = Math.Abs(_groupDelta); if (!ValidateBounds(group.boundsList, absDelta)) diff --git a/DLL/CSharp_TestApp/Program.cs b/DLL/CSharp_TestApp/Program.cs index 5400fc77..b0c491f1 100644 --- a/DLL/CSharp_TestApp/Program.cs +++ b/DLL/CSharp_TestApp/Program.cs @@ -93,7 +93,6 @@ public static void DisplayCPaths(T[]? cpaths, string spaceIndent) int idx = 2; for (int i = 0; i < pathCnt; i++) DisplayCPath(cpaths, ref idx, spaceIndent); - Console.Write("\n"); } // Note: The CPolyTree structure defined in clipper.export.h is @@ -121,7 +120,6 @@ public static void DisplayPolytree(T[] polytree, int precision) int idx = 2; for (int i = 0; i < cnt; i++) DisplayPolyPath(polytree, ref idx, false, " ", precision); - Console.Write("\n"); } public static T[]? GetArrayFromIntPtr(IntPtr paths) @@ -166,10 +164,14 @@ 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 = - "DisposeExportedCPaths64", CallingConvention = CallingConvention.Cdecl)] - static extern void DisposeExportedIntPtr(ref IntPtr intptr); + [DllImport(clipperDll, EntryPoint = + "DisposeArrayD", CallingConvention = CallingConvention.Cdecl)] + static extern void DisposeArrayD(ref IntPtr intptr); [DllImport(clipperDll, EntryPoint = "BooleanOp_PolyTree64", CallingConvention = CallingConvention.Cdecl)] @@ -184,6 +186,7 @@ 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; @@ -200,18 +203,18 @@ public static void Main() 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 }); - int result = BooleanOp64(Intersection, NonZero, cSubject, - null, cClip, out IntPtr cSol, out IntPtr cSolOpen, false, false); - if (result != 0) return; + + 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 - DisposeExportedIntPtr(ref cSol); - DisposeExportedIntPtr(ref cSolOpen); + 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 }); @@ -221,8 +224,8 @@ public static void Main() if (resultD != 0) return; double[]? cSolutionD = GetArrayFromIntPtr(cSolD); // clean up unmanaged memory - DisposeExportedIntPtr(ref cSolD); - DisposeExportedIntPtr(ref cSolOpenD); + DisposeArrayD(ref cSolD); + DisposeArrayD(ref cSolOpenD); DisplayCPaths(cSolutionD, " "); ///////////////////////////////////////////////////////////////////////// @@ -246,8 +249,8 @@ public static void Main() long[]? cPolyTree64 = GetArrayFromIntPtr(cSol_pt64); // clean up unmanaged memory - DisposeExportedIntPtr(ref cSol_pt64); - DisposeExportedIntPtr(ref cSolOpen_pt64); + DisposeArray64(ref cSol_pt64); + DisposeArray64(ref cSolOpen_pt64); if (cPolyTree64 == null) return; DisplayPolytree(cPolyTree64, 2); @@ -272,16 +275,16 @@ public static void Main() double[]? cPolyTreeD = GetArrayFromIntPtr(cSol_ptD); // clean up unmanaged memory - DisposeExportedIntPtr(ref cSol_ptD); - DisposeExportedIntPtr(ref cSolOpen_ptD); + DisposeArrayD(ref cSol_ptD); + DisposeArrayD(ref cSolOpen_ptD); if (cPolyTreeD == null) return; DisplayPolytree(cPolyTreeD, 2); ///////////////////////////////////////////////////////////////////////// - Console.WriteLine("Press ENTER to exit ... "); - Console.ReadLine(); + Console.WriteLine("\nPress any key to exit ... "); + Console.ReadKey(); } } //end Application diff --git a/DLL/CSharp_TestApp2/Program.cs b/DLL/CSharp_TestApp2/Program.cs index 85116bd2..845ac998 100644 --- a/DLL/CSharp_TestApp2/Program.cs +++ b/DLL/CSharp_TestApp2/Program.cs @@ -102,7 +102,7 @@ static long[] CreateCPaths64(Paths64 pp) Marshal.Copy(paths, len, 0, 1); long[] result = new long[len[0]]; Marshal.Copy(paths, result, 0, (int)len[0]); - return result; + return result; } // Define DLL exported functions ///////////////////// @@ -117,8 +117,8 @@ 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 = "DisposeExportedCPaths64", CallingConvention = CallingConvention.Cdecl)] - static extern void DisposeExportedCPaths64(ref IntPtr paths); + [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; @@ -133,28 +133,34 @@ public static void Main() Random rand = new(); //////////////////////////////////////////////////////////////////////// - int edgeCount = 2000; + int edgeCount = 2500; //////////////////////////////////////////////////////////////////////// Paths64 subject = new() { MakeRandomPath(600,400, edgeCount, rand)}; Paths64 clip = new() { MakeRandomPath(600, 400, edgeCount, rand) }; ////////////////////////////////////////////////////////////////////// - // Use Clipper2's C++ compiled library (ie use the DLL) - // NB: this time includes the overhead of swapping path structures + // 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); - int result = BooleanOp64(Intersection, NonZero, cSubject, - null, cClip, out IntPtr cSol, out IntPtr cSolOpen, false, false); + 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(); - if (result != 0) return; timeMsec = sw1.ElapsedMilliseconds; Console.WriteLine($"Time using DLL (C++ code): {timeMsec} ms"); ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// - // Use Clipper2's C# compiled library + // Use Clipper2's statically linked C# compiled library Stopwatch sw2 = Stopwatch.StartNew(); Clipper.Intersect(subject, clip, FillRule.NonZero); sw2.Stop(); @@ -162,14 +168,6 @@ public static void Main() Console.WriteLine($"Time using C# code : {timeMsec} ms"); ////////////////////////////////////////////////////////////////////// - long[]? cSolution = GetPathsFromIntPtr(cSol); - DisposeExportedCPaths64(ref cSol); - DisposeExportedCPaths64(ref cSolOpen); - if (cSolution == null) return; - - Paths64 solution = GetPaths64FromCPaths(cSolution); - //Console.WriteLine(solution.ToString()); - string fileName = "../../../clipper2_dll.svg"; SvgWriter svg = new(FillRule.NonZero); SvgUtils.AddSubject(svg, subject); @@ -178,8 +176,8 @@ public static void Main() svg.SaveToFile(fileName, 800, 600, 20); OpenFileWithDefaultApp(fileName); - //Console.WriteLine("Press ENTER to exit ... "); - //Console.ReadLine(); + Console.WriteLine("Press any key to exit ... "); + Console.ReadKey(); } } //end Application diff --git a/DLL/Delphi_TestApp/Test_DLL.dpr b/DLL/Delphi_TestApp/Test_DLL.dpr index 775dbc36..7869bd03 100644 --- a/DLL/Delphi_TestApp/Test_DLL.dpr +++ b/DLL/Delphi_TestApp/Test_DLL.dpr @@ -20,16 +20,18 @@ uses type CInt64arr = array[0..$FFFF] of Int64; - CPath64 = ^CInt64arr; - CPaths64 = ^CInt64arr; - CPolyPath64 = ^CInt64arr; - CPolytree64 = ^CInt64arr; + PCInt64arr = ^CInt64arr; + CPath64 = PCInt64arr; + CPaths64 = PCInt64arr; + CPolyPath64 = PCInt64arr; + CPolytree64 = PCInt64arr; CDblarr = array[0..$FFFF] of Double; - CPathD = ^CDblarr; - CPathsD = ^CDblarr; - CPolyPathD = ^CDblarr; - CPolytreeD = ^CDblarr; + PCDblarr = ^CDblarr; + CPathD = PCDblarr; + CPathsD = PCDblarr; + CPolyPathD = PCDblarr; + CPolytreeD = PCDblarr; const {$IFDEF WIN64} @@ -46,14 +48,10 @@ const function Version(): PAnsiChar; cdecl; external CLIPPER2_DLL name 'Version'; -procedure DisposeExportedCPaths64(var cps: CPaths64); cdecl; - external CLIPPER2_DLL name 'DisposeExportedCPaths64'; -procedure DisposeExportedCPathsD(var cp: CPathsD); cdecl; - external CLIPPER2_DLL name 'DisposeExportedCPathsD'; -procedure DisposeExportedCPolyTree64(var cpt: CPolyTree64); cdecl; - external CLIPPER2_DLL name 'DisposeExportedCPolyTree64'; -procedure DisposeExportedCPolyTreeD(var cpt: CPolyTreeD); cdecl; - external CLIPPER2_DLL name 'DisposeExportedCPolyTreeD'; +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; @@ -116,12 +114,12 @@ const // functions related to Clipper2 DLL structures //////////////////////////////////////////////////////// -procedure DisposeLocalCPaths64(cp: CPaths64); +procedure DisposeLocalArray64(cp: PCInt64arr); begin FreeMem(cp); end; -procedure DisposeLocalCPathsD(cp: CPathsD); +procedure DisposeLocalArrayD(cp: PCDblarr); begin FreeMem(cp); end; @@ -285,7 +283,7 @@ end; function CreatePolyPath64FromCPolyPath(var v: PInt64; owner: TPolyPath64): Boolean; var - i, magic, childCount, len: integer; + i, childCount, len: integer; path: TPath64; newOwner: TPolyPath64; begin @@ -308,7 +306,7 @@ end; function BuildPolyTree64FromCPolyTree(tree: CPolyTree64; outTree: TPolyTree64): Boolean; var v: PInt64; - i, magic, childCount, len: integer; + i, childCount: integer; begin Result := false; outTree.Clear(); @@ -322,7 +320,7 @@ end; function CreatePolyPathDFromCPolyPath(var v: PDouble; owner: TPolyPathD): Boolean; var - i, magic, childCount, len: integer; + i, len, childCount: integer; path: TPathD; newOwner: TPolyPathD; begin @@ -345,7 +343,7 @@ end; function BuildPolyTreeDFromCPolyTree(tree: CPolyTreeD; outTree: TPolyTreeD): Boolean; var v: PDouble; - i, magic, childCount, len: integer; + i, childCount: integer; begin Result := false; outTree.Clear(); @@ -499,10 +497,10 @@ begin ConvertToTPaths64(csol_extern), nil, 'BooleanOp64.svg'); // clean up - DisposeLocalCPaths64(csub_local); - DisposeLocalCPaths64(cclp_local); - DisposeExportedCPaths64(csol_extern); - DisposeExportedCPaths64(csolo_extern); + DisposeLocalArray64(csub_local); + DisposeLocalArray64(cclp_local); + DisposeExportedArray64(csol_extern); + DisposeExportedArray64(csolo_extern); end; procedure Test_BooleanOpD(edgeCnt: integer); @@ -534,10 +532,10 @@ begin DisplaySVG(sub, nil, clp, ConvertToTPathsD(csol_extern), nil, 'BooleanOpD.svg'); - DisposeLocalCPathsD(csub_local); - DisposeLocalCPathsD(cclp_local); - DisposeExportedCPathsD(csol_extern); - DisposeExportedCPathsD(csolo_extern); + DisposeLocalArrayD(csub_local); + DisposeLocalArrayD(cclp_local); + DisposeExportedArrayD(csol_extern); + DisposeExportedArrayD(csolo_extern); end; procedure Test_BooleanOp_Polytree64(edgeCnt: integer); @@ -569,11 +567,11 @@ begin finally tree.Free; end; - DisposeExportedCPolyTree64(csol_extern); - DisposeExportedCPaths64(csol_open_extern); + DisposeExportedArray64(csol_extern); + DisposeExportedArray64(csol_open_extern); - DisposeLocalCPaths64(csub_local); - DisposeLocalCPaths64(cclp_local); + DisposeLocalArray64(csub_local); + DisposeLocalArray64(cclp_local); // finally, display and clean up DisplaySVG(sub, nil, clp, sol, nil, 'BooleanOp_PolyTree64.svg'); @@ -608,11 +606,11 @@ begin finally tree.Free; end; - DisposeExportedCPolyTreeD(csol_extern); - DisposeExportedCPathsD(csol_open_extern); + DisposeExportedArrayD(csol_extern); + DisposeExportedArrayD(csol_open_extern); - DisposeLocalCPathsD(csub_local); - DisposeLocalCPathsD(cclp_local); + DisposeLocalArrayD(csub_local); + DisposeLocalArrayD(cclp_local); // finally, display and clean up DisplaySVG(sub, nil, clp, sol, nil, 'BooleanOp_PolyTreeD.svg'); @@ -648,70 +646,40 @@ begin DisplaySVG(sub, nil, nil, ConvertToTPathsD(csol_extern), nil, 'InflatePathsD.svg'); - DisposeLocalCPathsD(csub_local); - DisposeExportedCPathsD(csol_extern); - DisposeExportedCPathsD(csolo_extern); + DisposeLocalArrayD(csub_local); + DisposeExportedArrayD(csol_extern); + DisposeExportedArrayD(csolo_extern); end; -procedure Test_RectClipD(shapeCount: integer); +procedure Test_RectClipD(edgeCount: integer); var - i, rec_margin: Integer; - sub, clp, sol1, sol2: TPathsD; + rec_margin: Integer; + sub, clp, sol: TPathsD; csub_local: CPathsD; csol_extern: CPathsD; - scaleRnd, maxOffX, maxOffY: Double; rec: TRectD; - shapes: array [0..3] of TPathD; -const - w = 300; - h = 300; begin - // four simple concave polygons - shapes[0] := MakePathD([20,20, 20,0, 40,0, 40,20, 60,20, 60,40, - 40,40, 40,60, 20,60, 20,40, 0,40, 0,20]); - shapes[1] := MakePathD([0,0, 60,0, 60,20, 20,20, 20,40, 60,40, - 60,60, 0,60]); - shapes[2] := MakePathD([0,0, 20,0, 20,20, 40,20, 40,0, 60,0, - 60,60, 40,60, 40,40, 20,40, 20,60, 0,60]); - shapes[3] := MakePathD([20,60, 20,20, 0,20, 0,0, 60,0, 60,20, - 40,20, 40,60]); - - // setup - WriteLn(#10'Testing RectClip64:'); + WriteLn(#10'Testing RectClipD:'); - rec_margin := Min(w,h) div 3; + rec_margin := Min(displayWidth,displayHeight) div 4; rec.Left := rec_margin; rec.Top := rec_margin; - rec.Right := w - rec_margin; - rec.Bottom := h -rec_margin; - - SetLength(sub, shapeCount); - for i := 0 to shapeCount -1 do - begin - scaleRnd := (60 + Random(w div 4)) / 120; - maxOffX := w - (scaleRnd * 60); - maxOffY := h - (scaleRnd * 60); - sub[i] := ScalePathD(shapes[Random(4)], scaleRnd); - sub[i] := TranslatePath(sub[i], - Random(Round(maxOffX)), Random(Round(maxOffY))); - end; + 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); - sol1 := ConvertToTPathsD(csol_extern); - DisposeExportedCPathsD(csol_extern); - // do the DLL operation again with ConvexOnly disabled - csol_extern := RectClipD(rec, csub_local, 2, false); - sol2 := ConvertToTPathsD(csol_extern); + 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, sol2, nil, 'RectClip64_3.svg', w,h); - - DisposeLocalCPathsD(csub_local); - DisposeExportedCPathsD(csol_extern); + DisplaySVG(sub, nil, clp, sol, + nil, 'RectClipD.svg', displayWidth,displayHeight); end; procedure Test_RectClipLines64(edgeCnt: integer); @@ -742,16 +710,16 @@ begin DisplaySVG(nil, sub, clp, nil, ConvertToTPaths64(csolo_extern), 'RectClipLines64.svg'); - DisposeLocalCPaths64(csub_local); - DisposeExportedCPaths64(csolo_extern); + DisposeLocalArray64(csub_local); + DisposeExportedArray64(csolo_extern); end; //////////////////////////////////////////////////////// // main entry here //////////////////////////////////////////////////////// -var - s: string; +//var +// s: string; begin Randomize; Test_Version(); @@ -760,9 +728,9 @@ begin Test_BooleanOp_Polytree64(15); Test_BooleanOp_PolytreeD(25); Test_InflatePathsD(20, -10); // edgeCount, offsetDist - Test_RectClipD(15); + Test_RectClipD(7); Test_RectClipLines64(25); - WriteLn(#10'Press Enter to quit.'); - ReadLn(s); +// WriteLn(#10'Press Enter to quit.'); +// ReadLn(s); end. diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index 1ec5f664..fc57886b 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 8 November 2023 * +* Date : 15 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -428,7 +428,7 @@ procedure TClipperOffset.DoGroupOffset(group: TGroup); fGroupDelta := fDelta; end else begin - fGroupDelta := Abs(fDelta) * 0.5; + fGroupDelta := Abs(fDelta);// * 0.5; end; absDelta := Abs(fGroupDelta); From 7a481e87aac279b10da717872bcebd740c0d03f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20Loft=20H=C3=B8jbjerre?= <10060520+klauslh@users.noreply.github.com> Date: Fri, 17 Nov 2023 19:28:00 +0100 Subject: [PATCH 84/95] Fix simplification of open path (#714) * Add failing simplification test A slightly wiggly line from (0,0) to (0,100), which we expect to simplify to a straight line of length 100 with just the two endpoints remaining. Instead it simplifies to a much shorter line. * Don't recompute distance for open endpoint --- CPP/CMakeLists.txt | 1 + CPP/Clipper2Lib/include/clipper2/clipper.h | 3 ++- CPP/Tests/TestSimplifyPath.cpp | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 CPP/Tests/TestSimplifyPath.cpp diff --git a/CPP/CMakeLists.txt b/CPP/CMakeLists.txt index 0230ee10..9c419799 100644 --- a/CPP/CMakeLists.txt +++ b/CPP/CMakeLists.txt @@ -213,6 +213,7 @@ endif() Tests/TestPolytreeUnion.cpp Tests/TestRandomPaths.cpp Tests/TestRectClip.cpp + Tests/TestSimplifyPath.cpp Tests/TestTrimCollinear.cpp Tests/TestWindows.cpp ) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.h b/CPP/Clipper2Lib/include/clipper2/clipper.h index a2d9e84b..ce951dac 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.h @@ -705,7 +705,8 @@ namespace Clipper2Lib { curr = next; next = GetNext(next, high, flags); prior2 = GetPrior(prior, high, flags); - distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); + if (curr != high || isClosedPath) + distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); if (prior != 0 || isClosedPath) distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]); } diff --git a/CPP/Tests/TestSimplifyPath.cpp b/CPP/Tests/TestSimplifyPath.cpp new file mode 100644 index 00000000..61e701d4 --- /dev/null +++ b/CPP/Tests/TestSimplifyPath.cpp @@ -0,0 +1,14 @@ +#include "clipper2/clipper.h" +#include + +using namespace Clipper2Lib; + +TEST(Clipper2Tests, TestSimplifyPath) { + + Path64 input1 = MakePath({ + 0,0, 1,1, 0,20, 0,21, 1,40, 0,41, 0,60, 0,61, 0,80, 1,81, 0,100 + }); + Path64 output1 = SimplifyPath(input1, 2, false); + EXPECT_EQ(Length(output1), 100); + EXPECT_EQ(output1.size(), 2); +} From 60cd2938fa46721654c63b1f0e1743534096ef1b Mon Sep 17 00:00:00 2001 From: angusj Date: Sat, 18 Nov 2023 14:20:40 +1000 Subject: [PATCH 85/95] Caution: Fixed SimplifyPaths function in C# with confusing parameters Caution: Fixed SimplifyPaths function in Delphi with reversed boolean parameter. Minor code tidy to SimplifyPaths function. Reminder: Offsetting open path 'delta' behaviour also changed (ie doubled) recently. --- .../include/clipper2/clipper.export.h | 29 ++--- CPP/Clipper2Lib/include/clipper2/clipper.h | 32 +++-- CPP/Examples/Inflate/Inflate.cpp | 17 ++- .../Clipper2Lib.Examples/InflateDemo/Main.cs | 35 +++--- CSharp/Clipper2Lib/Clipper.cs | 76 ++++++------ Delphi/Clipper2Lib/Clipper.pas | 109 ++++++++++-------- 6 files changed, 151 insertions(+), 147 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index 7e789e19..1afd88f6 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -42,34 +42,35 @@ _______________________________ _______________________________ CPolytree64 and CPolytreeD: -These are also simple arrays consisting of CPolyPath structures. -However, since the very first CPolyPath is the tree container, it has no -path and its structure will be very slightly different from the remaining +These are also simple arrays consisting of CPolyPath structures that +represent individual paths in a tree structure. However, the very first +CPolyPath is just the tree container so it won'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. CPolyPath64 and CPolyPathD: -These are simple arrays consisting path coordinates followed by any number of -child CPolyPath (representing the paths they own). Preceding these are a pair -of values: the length of its path (N); and the number of child CPolyPath (C). +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 -(both directly and indirectly) for every other CPolyPath in the tree. So -where this first CPolyPath has no path, instead of a path length, its first -value contains the total length of the CPolytree array (A). +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 first +value will contain the total length of the CPolytree array. All the exported structures (CPaths64, CPathsD, CPolyTree64 & CPolyTreeD) are arrays of type int64_t or double. And the first value in these arrays will always contain the length of that array. -These array structures will be allocated in heap memory, so eventually this -memory will need to be released. But because the applications dynamically -linking to these functions may use a different memory manager, the only -really safe way to free up this memory is to use the exported -DisposeArray64 and DisposeArrayD functions below. +These array structures are allocated in heap memory which will eventually +need to be released. But since applications dynamically linking to these +functions may use different memory managers, the only really safe way to +free up this memory is to use the exported DisposeArray64 and +DisposeArrayD functions below. */ diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.h b/CPP/Clipper2Lib/include/clipper2/clipper.h index ce951dac..0f516b60 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 1 November 2023 * +* Date : 18 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -659,7 +659,7 @@ namespace Clipper2Lib { std::vector flags(len); std::vector distSqr(len); - size_t prior = high, curr = 0, start, next, prior2, next2; + size_t prior = high, curr = 0, start, next, prior2; if (isClosedPath) { distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]); @@ -689,27 +689,25 @@ namespace Clipper2Lib { next = GetNext(curr, high, flags); if (next == prior) break; + // flag for removal the smaller of adjacent 'distances' if (distSqr[next] < distSqr[curr]) { - flags[next] = true; - next = GetNext(next, high, flags); - next2 = GetNext(next, high, flags); - distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); - if (next != high || isClosedPath) - distSqr[next] = PerpendicDistFromLineSqrd(path[next], path[curr], path[next2]); + prior2 = prior; + prior = curr; curr = next; + next = GetNext(next, high, flags); } else - { - flags[curr] = true; - curr = next; - next = GetNext(next, high, flags); prior2 = GetPrior(prior, high, flags); - if (curr != high || isClosedPath) - distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); - if (prior != 0 || isClosedPath) - distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]); - } + + flags[curr] = true; + curr = next; + next = GetNext(next, high, flags); + + if (isClosedPath || ((curr != high) && (curr != 0))) + distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); + if (isClosedPath || ((prior != 0) && (prior != high))) + distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]); } Path result; result.reserve(len); diff --git a/CPP/Examples/Inflate/Inflate.cpp b/CPP/Examples/Inflate/Inflate.cpp index 298871e5..b7a7dcc7 100644 --- a/CPP/Examples/Inflate/Inflate.cpp +++ b/CPP/Examples/Inflate/Inflate.cpp @@ -11,42 +11,41 @@ void DoRabbit(); void DoSimpleShapes(); void System(const std::string& filename); + int main(int argc, char* argv[]) { + DoSimpleShapes(); DoRabbit(); - //std::getchar(); } void DoSimpleShapes() { - //open path offsets Paths64 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 })); - op2 = InflatePaths(op1, 20, JoinType::Miter, EndType::Square, 3); + op2 = InflatePaths(op1, 15, JoinType::Miter, EndType::Square, 3); SvgAddOpenSubject(svg2, op1, fr2, false); SvgAddSolution(svg2, TransformPaths(op2), fr2, false); SvgAddCaption(svg2, "Miter Joins; Square Ends", 20, 210); op1 = TranslatePaths(op1, 210, 0); - op2 = InflatePaths(op1, 20, JoinType::Square, EndType::Square); + op2 = InflatePaths(op1, 15, JoinType::Square, EndType::Square); SvgAddOpenSubject(svg2, op1, fr2, false); SvgAddSolution(svg2, TransformPaths(op2), fr2, false); SvgAddCaption(svg2, "Square Joins; Square Ends", 230, 210); op1 = TranslatePaths(op1, 210, 0); - op2 = InflatePaths(op1, 20, JoinType::Bevel, EndType::Butt, 3); + op2 = InflatePaths(op1, 15, JoinType::Bevel, EndType::Butt, 3); SvgAddOpenSubject(svg2, op1, fr2, false); SvgAddSolution(svg2, TransformPaths(op2), fr2, false); SvgAddCaption(svg2, "Bevel Joins; Butt Ends", 440, 210); op1 = TranslatePaths(op1, 210, 0); - op2 = InflatePaths(op1, 20, JoinType::Round, EndType::Round); + op2 = InflatePaths(op1, 15, JoinType::Round, EndType::Round); SvgAddOpenSubject(svg2, op1, fr2, false); SvgAddSolution(svg2, TransformPaths(op2), fr2, false); SvgAddCaption(svg2, "Round Joins; Round Ends", 650, 210); @@ -78,7 +77,7 @@ void DoSimpleShapes() p = TranslatePaths(p, 120, 100); pp.insert(pp.end(), p.begin(), p.end()); co.AddPaths(p, JoinType::Round, EndType::Joined); - co.Execute(20, p); + co.Execute(10, p); pp.insert(pp.end(), p.begin(), p.end()); FillRule fr3 = FillRule::EvenOdd; @@ -111,7 +110,7 @@ void DoRabbit() FillRule fr = FillRule::EvenOdd; SvgWriter svg; - SvgAddSolution(svg, solution, fr, false); + SvgAddSolution(svg, solution, fr, false); SvgSaveToFile(svg, "solution_off2.svg", 450, 720, 0); System("solution_off2.svg"); } diff --git a/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs b/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs index f7542aa2..33655c3b 100644 --- a/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs +++ b/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs @@ -6,6 +6,7 @@ * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ +using System; using System.IO; using System.Reflection; using Clipper2Lib; @@ -25,43 +26,39 @@ public static void Main() public static void DoSimpleShapes() { - //triangle offset - with large miter - Paths64 p = new() { Clipper.MakePath(new int[] { 30, 150, 60, 350, 0, 350 }) }; - Paths64 pp = new(); - pp.AddRange(p); + 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(); for (int i = 0; i < 5; ++i) { //nb: the last parameter here (10) greatly increases miter limit - p = Clipper.InflatePaths(p, 5, JoinType.Miter, EndType.Polygon, 10); - pp.AddRange(p); + p0 = Clipper.InflatePaths(p0, 5, JoinType.Miter, EndType.Polygon, 10); + p.AddRange(p0); } + SvgUtils.AddSolution(svg, p, false); + p.Clear(); //rectangle offset - both squared and rounded - p.Clear(); - ClipperOffset co = new(); - //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 })); - pp.AddRange(p); + 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); p = Clipper.TranslatePaths(p, 60, 50); - pp.AddRange(p); + SvgUtils.AddOpenSubject(svg, p); co.AddPaths(p, JoinType.Square, EndType.Joined); - p = Clipper.TranslatePaths(p, 60, 50); - pp.AddRange(p); + SvgUtils.AddOpenSubject(svg, p); co.AddPaths(p, JoinType.Round, EndType.Joined); - - co.Execute(20, p); - pp.AddRange(p); + co.Execute(10, p); string filename = "../../../inflate.svg"; - SvgWriter svg = new(); - SvgUtils.AddSolution(svg, pp, false); + 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); diff --git a/CSharp/Clipper2Lib/Clipper.cs b/CSharp/Clipper2Lib/Clipper.cs index af6a077c..6b63f05d 100644 --- a/CSharp/Clipper2Lib/Clipper.cs +++ b/CSharp/Clipper2Lib/Clipper.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 1 October 2023 * +* Date : 18 October 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module contains simple functions that will likely cover * @@ -850,8 +850,8 @@ private static int GetPrior(int current, int high, ref bool[] flags) return current; } - public static Path64 SimplifyPath(Path64 path, - double epsilon, bool isClosedPath = false) + public static Path64 SimplifyPath(Path64 path, + double epsilon, bool isClosedPath = true) { int len = path.Count, high = len - 1; double epsSqr = Sqr(epsilon); @@ -859,7 +859,7 @@ public static Path64 SimplifyPath(Path64 path, bool[] flags = new bool[len]; double[] dsq = new double[len]; - int curr = 0, prev, start, next, prior2, next2; + int curr = 0, prev, start, next, prior2; if (isClosedPath) { @@ -893,24 +893,21 @@ public static Path64 SimplifyPath(Path64 path, if (dsq[next] < dsq[curr]) { - flags[next] = true; - next = GetNext(next, high, ref flags); - next2 = GetNext(next, high, ref flags); - dsq[curr] = PerpendicDistFromLineSqrd(path[curr], path[prev], path[next]); - if (next != high || isClosedPath) - dsq[next] = PerpendicDistFromLineSqrd(path[next], path[curr], path[next2]); + prior2 = prev; + prev = curr; curr = next; + next = GetNext(next, high, ref flags); } else - { - flags[curr] = true; - curr = next; - next = GetNext(next, high, ref flags); prior2 = GetPrior(prev, high, ref flags); + + flags[curr] = true; + curr = next; + next = GetNext(next, high, ref flags); + if (isClosedPath || ((curr != high) && (curr != 0))) dsq[curr] = PerpendicDistFromLineSqrd(path[curr], path[prev], path[next]); - if (prev != 0 || isClosedPath) - dsq[prev] = PerpendicDistFromLineSqrd(path[prev], path[prior2], path[curr]); - } + if (isClosedPath || ((prev != 0) && (prev != high))) + dsq[prev] = PerpendicDistFromLineSqrd(path[prev], path[prior2], path[curr]); } Path64 result = new Path64(len); for (int i = 0; i < len; i++) @@ -919,7 +916,7 @@ public static Path64 SimplifyPath(Path64 path, } public static Paths64 SimplifyPaths(Paths64 paths, - double epsilon, bool isClosedPaths = false) + double epsilon, bool isClosedPaths = true) { Paths64 result = new Paths64(paths.Count); foreach (Path64 path in paths) @@ -928,7 +925,7 @@ public static Paths64 SimplifyPaths(Paths64 paths, } public static PathD SimplifyPath(PathD path, - double epsilon, bool isOpenPath = false) + double epsilon, bool isClosedPath = true) { int len = path.Count, high = len - 1; double epsSqr = Sqr(epsilon); @@ -936,16 +933,16 @@ public static PathD SimplifyPath(PathD path, bool[] flags = new bool[len]; double[] dsq = new double[len]; - int curr = 0, prev, start, next, prior2, next2; - if (isOpenPath) + int curr = 0, prev, start, next, prior2; + if (isClosedPath) { - dsq[0] = double.MaxValue; - dsq[high] = double.MaxValue; + dsq[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]); + dsq[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]); } else { - dsq[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]); - dsq[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]); + dsq[0] = double.MaxValue; + dsq[high] = double.MaxValue; } for (int i = 1; i < high; ++i) dsq[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]); @@ -968,24 +965,21 @@ public static PathD SimplifyPath(PathD path, if (dsq[next] < dsq[curr]) { - flags[next] = true; - next = GetNext(next, high, ref flags); - next2 = GetNext(next, high, ref flags); - dsq[curr] = PerpendicDistFromLineSqrd(path[curr], path[prev], path[next]); - if (next != high || !isOpenPath) - dsq[next] = PerpendicDistFromLineSqrd(path[next], path[curr], path[next2]); - curr = next; - } - else - { - flags[curr] = true; + prior2 = prev; + prev = curr; curr = next; next = GetNext(next, high, ref flags); + } + else prior2 = GetPrior(prev, high, ref flags); + + flags[curr] = true; + curr = next; + next = GetNext(next, high, ref flags); + if (isClosedPath || ((curr != high) && (curr != 0))) dsq[curr] = PerpendicDistFromLineSqrd(path[curr], path[prev], path[next]); - if (prev != 0 || !isOpenPath) - dsq[prev] = PerpendicDistFromLineSqrd(path[prev], path[prior2], path[curr]); - } + if (isClosedPath || ((prev != 0) && (prev != high))) + dsq[prev] = PerpendicDistFromLineSqrd(path[prev], path[prior2], path[curr]); } PathD result = new PathD(len); for (int i = 0; i < len; i++) @@ -994,11 +988,11 @@ public static PathD SimplifyPath(PathD path, } public static PathsD SimplifyPaths(PathsD paths, - double epsilon, bool isOpenPath = false) + double epsilon, bool isClosedPath = true) { PathsD result = new PathsD(paths.Count); foreach (PathD path in paths) - result.Add(SimplifyPath(path, epsilon, isOpenPath)); + result.Add(SimplifyPath(path, epsilon, isClosedPath)); return result; } diff --git a/Delphi/Clipper2Lib/Clipper.pas b/Delphi/Clipper2Lib/Clipper.pas index 73fb3260..1d34c92b 100644 --- a/Delphi/Clipper2Lib/Clipper.pas +++ b/Delphi/Clipper2Lib/Clipper.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 17 July 2023 * +* Date : 18 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module provides a simple interface to the Clipper Library * @@ -17,9 +17,8 @@ interface Math, SysUtils, Classes, Clipper.Core, Clipper.Engine, Clipper.Offset, Clipper.RectClip; -// Redeclare here a number of structures defined in -// other units so those units won't need to be declared -// just to use the following functions. +// A number of structures defined in other units are redeclared here +// so those units won't also need to be declared in your own units clauses. type TClipper = Clipper.Engine.TClipper64; TClipper64 = Clipper.Engine.TClipper64; @@ -148,9 +147,13 @@ function PointInPolygon(const pt: TPoint64; const polygon: TPath64): TPointInPolygonResult; function SimplifyPath(const path: TPath64; - shapeTolerance: double; isOpenPath: Boolean): TPath64; + shapeTolerance: double; isClosedPath: Boolean = true): TPath64; overload; function SimplifyPaths(const paths: TPaths64; - shapeTolerance: double; isOpenPaths: Boolean): TPaths64; + shapeTolerance: double; isClosedPath: Boolean = true): TPaths64; overload; +function SimplifyPath(const path: TPathD; shapeTolerance: double; + isClosedPath: Boolean = true; decimalPrecision: integer = 2): TPathD; overload; +function SimplifyPaths(const paths: TPathsD; shapeTolerance: double; + isClosedPath: Boolean = true; decimalPrecision: integer = 2): TPathsD; overload; implementation @@ -848,22 +851,22 @@ TSimplifyRec = record pdSqrd : double; prev : PSimplifyRec; next : PSimplifyRec; - isEnd : Boolean; + //isEnd : Boolean; end; function SimplifyPath(const path: TPath64; - shapeTolerance: double; isOpenPath: Boolean): TPath64; + shapeTolerance: double; isClosedPath: Boolean): TPath64; var - i, highI, minLen: integer; + i, highI, minHigh: integer; tolSqrd: double; srArray: array of TSimplifyRec; first, last: PSimplifyRec; begin Result := nil; highI := High(path); - if isOpenPath then minLen := 2 else minLen := 3; - if highI +1 < minLen then Exit; + if isClosedPath then minHigh := 2 else minHigh := 1; + if highI < minHigh then Exit; SetLength(srArray, highI +1); with srArray[0] do @@ -871,15 +874,9 @@ function SimplifyPath(const path: TPath64; pt := path[0]; prev := @srArray[highI]; next := @srArray[1]; - if isOpenPath then - begin - pdSqrd := MaxDouble; - isEnd := true; - end else - begin - pdSqrd := PerpendicDistSqrd(path[0], path[highI], path[1]); - isEnd := false; - end; + if isClosedPath then + pdSqrd := PerpendicDistSqrd(path[0], path[highI], path[1]) else + pdSqrd := invalidD; end; with srArray[highI] do @@ -887,15 +884,9 @@ function SimplifyPath(const path: TPath64; pt := path[highI]; prev := @srArray[highI-1]; next := @srArray[0]; - if isOpenPath then - begin - pdSqrd := MaxDouble; - isEnd := true; - end else - begin - pdSqrd := PerpendicDistSqrd(path[highI], path[highI-1], path[0]); - isEnd := false; - end; + if isClosedPath then + pdSqrd := PerpendicDistSqrd(path[highI], path[highI-1], path[0]) else + pdSqrd := invalidD; end; for i := 1 to highI -1 do @@ -905,7 +896,6 @@ function SimplifyPath(const path: TPath64; prev := @srArray[i-1]; next := @srArray[i+1]; pdSqrd := PerpendicDistSqrd(path[i], path[i-1], path[i+1]); - isEnd := false; end; first := @srArray[0]; @@ -914,26 +904,23 @@ function SimplifyPath(const path: TPath64; tolSqrd := Sqr(shapeTolerance); while first <> last do begin - if first.isEnd or (first.pdSqrd > tolSqrd) or + if (first.pdSqrd > tolSqrd) or (first.next.pdSqrd < first.pdSqrd) then begin first := first.next; - end else - begin - first.prev.next := first.next; - first.next.prev := first.prev; - last := first.prev; - dec(highI); - if last.next = last.prev then break; - last.pdSqrd := - PerpendicDistSqrd(last.pt, last.prev.pt, last.next.pt); - first := last.next; - first.pdSqrd := - PerpendicDistSqrd(first.pt, first.prev.pt, first.next.pt); + Continue; end; + dec(highI); + first.prev.next := first.next; + first.next.prev := first.prev; + last := first.prev; + first := last.next; + if first.next = first.prev then break; + last.pdSqrd := PerpendicDistSqrd(last.pt, last.prev.pt, first.pt); + first.pdSqrd := PerpendicDistSqrd(first.pt, last.pt, first.next.pt); end; - if highI +1 < minLen then Exit; - if isOpenPath then first := @srArray[0]; + if highI < minHigh then Exit; + if not isClosedPath then first := @srArray[0]; SetLength(Result, highI +1); for i := 0 to HighI do begin @@ -944,15 +931,43 @@ function SimplifyPath(const path: TPath64; //------------------------------------------------------------------------------ function SimplifyPaths(const paths: TPaths64; - shapeTolerance: double; isOpenPaths: Boolean): TPaths64; + shapeTolerance: double; isClosedPath: Boolean): TPaths64; var i, len: integer; begin len := Length(paths); SetLength(Result, len); for i := 0 to len -1 do - result[i] := SimplifyPath(paths[i], shapeTolerance, isOpenPaths); + result[i] := SimplifyPath(paths[i], shapeTolerance, isClosedPath); end; +//------------------------------------------------------------------------------ + +function SimplifyPath(const path: TPathD; shapeTolerance: double; + isClosedPath: Boolean; decimalPrecision: integer): TPathD; +var + p: TPath64; + scale: double; +begin + scale := power(10, decimalPrecision); + p := ScalePath(path, scale); + p := SimplifyPath(p, shapeTolerance, isClosedPath); + Result := ScalePathD(p, 1/scale); +end; +//------------------------------------------------------------------------------ + +function SimplifyPaths(const paths: TPathsD; shapeTolerance: double; + isClosedPath: Boolean; decimalPrecision: integer): TPathsD; +var + pp: TPaths64; + scale: double; +begin + scale := power(10, decimalPrecision); + pp := ScalePaths(paths, scale); + pp := SimplifyPaths(pp, shapeTolerance, isClosedPath); + Result := ScalePathsD(pp, 1/scale); +end; +//------------------------------------------------------------------------------ + end. From 466efe768b11d140a18e12639772fed5ac92561c Mon Sep 17 00:00:00 2001 From: Angus Johnson Date: Sat, 18 Nov 2023 16:34:58 +1000 Subject: [PATCH 86/95] Update ReadMe.md --- DLL/ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DLL/ReadMe.md b/DLL/ReadMe.md index 2f39f6db..13ce205c 100644 --- a/DLL/ReadMe.md +++ b/DLL/ReadMe.md @@ -1,6 +1,6 @@ # Clipper2 DLL -The Clipper2 library is written in several languages (C++, C# & Delphi Pascal) and can be compiled and statically linked into applications using those languages. Now developers using other programming languages can also access almost all of Clipper2's features by dynamically linking to a C++ compiled library. (The latest precompiled DLLs can be found in [Clipper2's Releases](https://github.com/AngusJohnson/Clipper2/releases)). +The Clipper2 library is written in several languages (C++, C# & Delphi Pascal) and can be compiled and statically linked into applications using those languages. Now developers using other programming languages can also access almost all of Clipper2's features by dynamically linking to a C++ compiled library. Exported functions and their related data structures are well documented in [clipper.export.h](https://github.com/AngusJohnson/Clipper2/blob/main/CPP/Clipper2Lib/include/clipper2/clipper.export.h). And the latest precompiled DLLs can be found in [Clipper2's Releases](https://github.com/AngusJohnson/Clipper2/releases). This folder contains: 1. C++ code that compiles **Clipper2** into Windows DLLs (both 32bit and 64bit). From 49558735238e3a788f2e86279ab69306b0a5da00 Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 19 Nov 2023 14:19:21 +1000 Subject: [PATCH 87/95] Fixed bug where offsetting would occasionally lose a path (#715) --- .../include/clipper2/clipper.offset.h | 3 +- CPP/Clipper2Lib/src/clipper.offset.cpp | 44 ++++++++----- CPP/Tests/TestOffsets.cpp | 24 ++++++- CSharp/Clipper2Lib/Clipper.Offset.cs | 65 ++++++++++++------- Delphi/Clipper2Lib/Clipper.Offset.pas | 43 ++++++++---- 5 files changed, 127 insertions(+), 52 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.offset.h b/CPP/Clipper2Lib/include/clipper2/clipper.offset.h index a1b16b5e..30992bfa 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.offset.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.offset.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 5 November 2023 * +* Date : 19 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -34,6 +34,7 @@ class ClipperOffset { class Group { public: Paths64 paths_in; + std::vector is_hole_list; std::vector bounds_list; int lowest_path_idx = -1; bool is_reversed = false; diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index eaf514cf..b8ee4481 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 15 November 2023 * +* Date : 19 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -160,16 +160,26 @@ ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType for (Path64& p: paths_in) StripDuplicates(p, is_joined); + // get bounds of each path --> bounds_list GetMultiBounds(paths_in, bounds_list, end_type); + if (end_type == EndType::Polygon) { + is_hole_list.reserve(paths_in.size()); + for (const Path64& path : paths_in) + is_hole_list.push_back(Area(path) < 0); lowest_path_idx = GetLowestClosedPathIdx(bounds_list); - is_reversed = (lowest_path_idx >= 0) && Area(paths_in[lowest_path_idx]) < 0; + // 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) && is_hole_list[lowest_path_idx]; + if (is_reversed) is_hole_list.flip(); } else { lowest_path_idx = -1; is_reversed = false; + is_hole_list.resize(paths_in.size()); } } @@ -541,18 +551,20 @@ void ClipperOffset::DoGroupOffset(Group& group) steps_per_rad_ = steps_per_360 / (2 * PI); } - size_t i = 0; - for (const Path64& path_in : group.paths_in) + + std::vector::const_iterator path_rect_it = group.bounds_list.cbegin(); + std::vector::const_iterator is_hole_it = group.is_hole_list.cbegin(); + Paths64::const_iterator path_in_it = group.paths_in.cbegin(); + for ( ; path_in_it != group.paths_in.cend(); ++path_in_it, ++path_rect_it, ++is_hole_it) { - const Rect64& path_rect = group.bounds_list[i++]; - if (!path_rect.IsValid()) continue; - Path64::size_type pathLen = path_in.size(); + if (!path_rect_it->IsValid()) continue; + Path64::size_type pathLen = path_in_it->size(); path_out.clear(); if (pathLen == 1) // single point - only valid with open paths { if (group_delta_ < 1) continue; - const Point64& pt = path_in[0]; + const Point64& pt = (*path_in_it)[0]; //single vertex so build a circle or square ... if (group.join_type == JoinType::Round) { @@ -577,19 +589,20 @@ void ClipperOffset::DoGroupOffset(Group& group) } // end of offsetting a single (open path) point // when shrinking, then make sure the path can shrink that far (#593) - if (group_delta_ < 0 && - std::min(path_rect.Width(), path_rect.Height()) < -group_delta_ * 2) - continue; + // but also make sure this isn't a hole which will be expanding (#715) + if ( ((group_delta_ < 0) != *is_hole_it) && + (std::min(path_rect_it->Width(), path_rect_it->Height()) < -group_delta_ * 2) ) + continue; if ((pathLen == 2) && (group.end_type == EndType::Joined)) end_type_ = (group.join_type == JoinType::Round) ? EndType::Round : EndType::Square; - BuildNormals(path_in); - if (end_type_ == EndType::Polygon) OffsetPolygon(group, path_in); - else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, path_in); - else OffsetOpenPath(group, path_in); + BuildNormals(*path_in_it); + if (end_type_ == EndType::Polygon) OffsetPolygon(group, *path_in_it); + else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, *path_in_it); + else OffsetOpenPath(group, *path_in_it); } } @@ -604,6 +617,7 @@ size_t ClipperOffset::CalcSolutionCapacity() bool ClipperOffset::CheckReverseOrientation() { + // nb: this assumes there's consistency in orientation between groups bool is_reversed_orientation = false; for (const Group& g : groups_) if (g.end_type == EndType::Polygon) diff --git a/CPP/Tests/TestOffsets.cpp b/CPP/Tests/TestOffsets.cpp index 84776ad9..494a8da3 100644 --- a/CPP/Tests/TestOffsets.cpp +++ b/CPP/Tests/TestOffsets.cpp @@ -399,9 +399,31 @@ TEST(Clipper2Tests, TestOffsets6) // also modified from #593 (tests rounded ends offseter.AddPaths(subjects, Clipper2Lib::JoinType::Round, Clipper2Lib::EndType::Polygon); Clipper2Lib::Paths64 solution; offseter.Execute(offset, solution); - + EXPECT_EQ(solution.size(), 2); double area = Area(solution[1]); EXPECT_LT(area, -47500); } + +TEST(Clipper2Tests, TestOffsets7) // (#593 & #715) +{ + Paths64 solution; + Paths64 subject = { MakePath({0,0, 100,0, 100,100, 0,100}) }; + + solution = InflatePaths(subject, -50, JoinType::Miter, EndType::Polygon); + EXPECT_EQ(solution.size(), 0); + + subject.push_back(MakePath({ 40,60, 60,60, 60,40, 40,40 })); + solution = InflatePaths(subject, 10, JoinType::Miter, EndType::Polygon); + EXPECT_EQ(solution.size(), 1); + + reverse(subject[0].begin(), subject[0].end()); + reverse(subject[1].begin(), subject[1].end()); + solution = InflatePaths(subject, 10, JoinType::Miter, EndType::Polygon); + EXPECT_EQ(solution.size(), 1); + + subject.resize(1); + solution = InflatePaths(subject, -50, JoinType::Miter, EndType::Polygon); + EXPECT_EQ(solution.size(), 0); +} diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index cf5479e4..b4fecb97 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 15 November 2023 * +* Date : 19 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -10,8 +10,6 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Text.RegularExpressions; -using Clipper2Lib; namespace Clipper2Lib { @@ -39,6 +37,7 @@ private class Group { internal Paths64 inPaths; internal List boundsList; + internal List isHoleList; internal JoinType joinType; internal EndType endType; internal bool pathsReversed; @@ -54,12 +53,30 @@ public Group(Paths64 paths, JoinType joinType, EndType endType = EndType.Polygon foreach(Path64 path in paths) inPaths.Add(Clipper.StripDuplicates(path, isJoined)); - boundsList = new List(); + // get bounds of each path --> boundsList + boundsList = new List(inPaths.Count); GetMultiBounds(inPaths, boundsList, endType); - lowestPathIdx = GetLowestPathIdx(boundsList); - pathsReversed = false; + if (endType == EndType.Polygon) - pathsReversed = Clipper.Area(inPaths[lowestPathIdx]) < 0; + { + lowestPathIdx = GetLowestPathIdx(boundsList); + isHoleList = new List(inPaths.Count); + + foreach (Path64 path in inPaths) + isHoleList.Add(Clipper.Area(path) < 0); + // the lowermost path must be an outer path, so if its orientation is negative, + // then flag that the whole group is 'reversed' (will negate delta etc.) + // as this is much more efficient than reversing every path. + pathsReversed = (lowestPathIdx >= 0) && isHoleList[lowestPathIdx]; + if (pathsReversed) + for (int i = 0; i < isHoleList.Count; i++) isHoleList[i] = !isHoleList[i]; + } + else + { + lowestPathIdx = -1; + isHoleList = new List(new bool[inPaths.Count]); + pathsReversed = false; + } } } @@ -703,35 +720,38 @@ private void DoGroupOffset(Group group) _stepsPerRad = stepsPer360 / (2 * Math.PI); } - int i = 0; - foreach (Path64 p in group.inPaths) + using List.Enumerator pathIt = group.inPaths.GetEnumerator(); + using List.Enumerator boundsIt = group.boundsList.GetEnumerator(); + using List.Enumerator isHoleIt = group.isHoleList.GetEnumerator(); + while (pathIt.MoveNext() && boundsIt.MoveNext() && isHoleIt.MoveNext()) { - Rect64 pathBounds = group.boundsList[i++]; + Rect64 pathBounds = boundsIt.Current; if (!pathBounds.IsValid()) continue; - int cnt = p.Count; - if ((cnt == 0) || ((cnt < 3) && (_endType == EndType.Polygon))) - continue; + Path64 p = pathIt.Current; + bool isHole = isHoleIt.Current; pathOut = new Path64(); + int cnt = p.Count; + if (cnt == 1) { Point64 pt = p[0]; - + // 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); + int steps = (int) Math.Ceiling(_stepsPerRad * 2 * Math.PI); pathOut = Clipper.Ellipse(pt, r, r, steps); #if USINGZ pathOut = InternalClipper.SetZ(pathOut, pt.Z); -#endif +#endif } else { int d = (int) Math.Ceiling(_groupDelta); - Rect64 r = new Rect64(pt.X - d, pt.Y - d, pt.X + d, pt.Y + d); + 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); @@ -742,13 +762,14 @@ private void DoGroupOffset(Group group) } // end of offsetting a single (open path) point // when shrinking, then make sure the path can shrink that far (#593) - if (_groupDelta < 0 && - Math.Min(pathBounds.Width, pathBounds.Height) < -_groupDelta * 2) - continue; + // but also make sure this isn't a hole which will be expanding (#715) + if (((_groupDelta < 0) != isHole) && + (Math.Min(pathBounds.Width, pathBounds.Height) < -_groupDelta * 2)) + continue; if (cnt == 2 && group.endType == EndType.Joined) - _endType = (group.joinType == JoinType.Round) ? - EndType.Round : + _endType = (group.joinType == JoinType.Round) ? + EndType.Round : EndType.Square; BuildNormals(p); diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index fc57886b..560822f2 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 15 November 2023 * +* Date : 19 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -33,6 +33,7 @@ interface const path_norms: TPathD; currIdx, prevIdx: integer): double of object; TRect64Array = array of TRect64; + BooleanArray = array of Boolean; TGroup = class paths : TPaths64; @@ -41,6 +42,7 @@ TGroup = class reversed : Boolean; lowestPathIdx: integer; boundsList: TRect64Array; + isHoleList: BooleanArray; constructor Create(const pathsIn: TPaths64; jt: TJoinType; et: TEndType); end; @@ -139,7 +141,7 @@ implementation // Miscellaneous offset support functions //------------------------------------------------------------------------------ -function GetMultiBounds(const paths: TPaths64; endType: TEndType): TRect64Array; +procedure GetMultiBounds(const paths: TPaths64; var list: TRect64Array; endType: TEndType); var i,j, len, len2, minPathLen: integer; path: TPath64; @@ -150,14 +152,13 @@ function GetMultiBounds(const paths: TPaths64; endType: TEndType): TRect64Array; minPathLen := 3 else minPathLen := 1; len := Length(paths); - SetLength(Result, len); for i := 0 to len -1 do begin path := paths[i]; len2 := Length(path); if len2 < minPathLen then begin - Result[i] := InvalidRect64; + list[i] := InvalidRect64; continue; end; pt1 := path[0]; @@ -170,7 +171,7 @@ function GetMultiBounds(const paths: TPaths64; endType: TEndType): TRect64Array; if (pt.x > r.right) then r.right := pt.x else if (pt.x < r.left) then r.left := pt.x; end; - Result[i] := r; + list[i] := r; end; end; //------------------------------------------------------------------------------ @@ -308,6 +309,7 @@ constructor TGroup.Create(const pathsIn: TPaths64; jt: TJoinType; et: TEndType); var i, len: integer; isJoined: boolean; + pb: PBoolean; begin Self.joinType := jt; Self.endType := et; @@ -318,17 +320,31 @@ constructor TGroup.Create(const pathsIn: TPaths64; jt: TJoinType; et: TEndType); for i := 0 to len -1 do paths[i] := StripDuplicates(pathsIn[i], isJoined); - boundsList := GetMultiBounds(paths, et); + reversed := false; + SetLength(isHoleList, len); + SetLength(boundsList, len); + GetMultiBounds(paths, boundsList, et); if (et = etPolygon) then begin lowestPathIdx := GetLowestClosedPathIdx(boundsList); - reversed := (lowestPathIdx >= 0) and ( - Area(pathsIn[lowestPathIdx]) < 0); + if lowestPathIdx < 0 then Exit; + pb := @isHoleList[0]; + for i := 0 to len -1 do + begin + pb^ := Area(paths[i]) < 0; inc(pb); + end; + // the lowermost path must be an outer path, so if its orientation is + // negative, then flag that the whole group is 'reversed' (so negate + // delta etc.) as this is much more efficient than reversing every path. + reversed := (lowestPathIdx >= 0) and isHoleList[lowestPathIdx]; + if not reversed then Exit; + pb := @isHoleList[0]; + for i := 0 to len -1 do + begin + pb^ := not pb^; inc(pb); + end; end else - begin lowestPathIdx := -1; - reversed := false; - end; end; //------------------------------------------------------------------------------ @@ -495,8 +511,9 @@ procedure TClipperOffset.DoGroupOffset(group: TGroup); Continue; end; - // when shrinking, then make sure the path can shrink that far - if (fGroupDelta < 0) and + // when shrinking, then make sure the path can shrink that far (#593) + // but also make sure this isn't a hole which will be expanding (#715) + if ((fGroupDelta < 0) <> group.isHoleList[i]) and (Min(group.boundsList[i].Width, group.boundsList[i].Height) < fGroupDelta *2) then Continue; From 9299eb6cbf683ee1545c31dbf14c67a5c5def1ff Mon Sep 17 00:00:00 2001 From: angusj Date: Wed, 22 Nov 2023 21:06:59 +1000 Subject: [PATCH 88/95] Caution: Clipper.ReverseSolution and Clipper.PreserveCollinear now method calls in C++. Fixed an obscure clipping bug (#720) --- .../include/clipper2/clipper.core.h | 20 ++-- .../include/clipper2/clipper.engine.h | 10 +- .../include/clipper2/clipper.export.h | 18 ++-- CPP/Clipper2Lib/src/clipper.engine.cpp | 91 +++++++++---------- CPP/Clipper2Lib/src/clipper.offset.cpp | 10 +- CPP/Tests/TestPolygons.cpp | 16 +++- CPP/Tests/TestPolytreeUnion.cpp | 2 +- 7 files changed, 94 insertions(+), 73 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index 0d9ec7b1..e3e9824c 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 1 November 2023 * +* Date : 22 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library structures and functions * @@ -43,13 +43,16 @@ namespace Clipper2Lib "Invalid scale (either 0 or too large)"; static const char* non_pair_error = "There must be 2 values for each coordinate"; + static const char* undefined_error = + "There is an undefined error in Clipper2"; #endif // error codes (2^n) - const int precision_error_i = 1; // non-fatal - const int scale_error_i = 2; // non-fatal - const int non_pair_error_i = 4; // non-fatal - const int range_error_i = 64; + const int precision_error_i = 1; // non-fatal + const int scale_error_i = 2; // non-fatal + const int non_pair_error_i = 4; // non-fatal + const int undefined_error_i = 32; // fatal + const int range_error_i = 64; #ifndef PI static const double PI = 3.141592653589793238; @@ -80,6 +83,8 @@ namespace Clipper2Lib throw Clipper2Exception(scale_error); case non_pair_error_i: throw Clipper2Exception(non_pair_error); + case undefined_error_i: + throw Clipper2Exception(undefined_error); case range_error_i: throw Clipper2Exception(range_error); } @@ -88,6 +93,7 @@ namespace Clipper2Lib #endif } + //By far the most widely used filling rules for polygons are EvenOdd //and NonZero, sometimes called Alternate and Winding respectively. //https://en.wikipedia.org/wiki/Nonzero-rule @@ -144,7 +150,7 @@ namespace Clipper2Lib friend std::ostream& operator<<(std::ostream& os, const Point& point) { - os << point.x << "," << point.y << "," << point.z << " "; + os << point.x << "," << point.y << "," << point.z; return os; } @@ -181,7 +187,7 @@ namespace Clipper2Lib friend std::ostream& operator<<(std::ostream& os, const Point& point) { - os << point.x << "," << point.y << " "; + os << point.x << "," << point.y; return os; } #endif diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h index fb95f69d..cc41eb03 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 25 October 2023 * +* Date : 22 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -262,6 +262,8 @@ namespace Clipper2Lib { inline void CheckJoinRight(Active& e, const Point64& pt, bool check_curr_x = false); protected: + bool preserve_collinear_ = true; + bool reverse_solution_ = false; int error_code_ = 0; bool has_open_paths_ = false; bool succeeded_ = true; @@ -281,8 +283,10 @@ namespace Clipper2Lib { public: virtual ~ClipperBase(); int ErrorCode() { return error_code_; }; - bool PreserveCollinear = true; - bool ReverseSolution = false; + void PreserveCollinear(bool val) { preserve_collinear_ = val; }; + bool PreserveCollinear() { return preserve_collinear_;}; + void ReverseSolution(bool val) { reverse_solution_ = val; }; + bool ReverseSolution() { return reverse_solution_; }; void Clear(); void AddReuseableData(const ReuseableDataContainer64& reuseable_data); #ifdef USINGZ diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index 1afd88f6..dbaca63b 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 15 November 2023 * +* Date : 22 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module exports the Clipper2 Library (ie DLL/so) * @@ -377,8 +377,8 @@ EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, clp = ConvertCPaths(clips); Clipper64 clipper; - clipper.PreserveCollinear = preserve_collinear; - clipper.ReverseSolution = reverse_solution; + clipper.PreserveCollinear(preserve_collinear); + clipper.ReverseSolution(reverse_solution); if (sub.size() > 0) clipper.AddSubject(sub); if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); if (clp.size() > 0) clipper.AddClip(clp); @@ -404,8 +404,8 @@ EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype, PolyTree64 tree; Clipper64 clipper; - clipper.PreserveCollinear = preserve_collinear; - clipper.ReverseSolution = reverse_solution; + clipper.PreserveCollinear(preserve_collinear); + clipper.ReverseSolution(reverse_solution); if (sub.size() > 0) clipper.AddSubject(sub); if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); if (clp.size() > 0) clipper.AddClip(clp); @@ -434,8 +434,8 @@ EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, clp = ConvertCPathsDToPaths64(clips, scale); Clipper64 clipper; - clipper.PreserveCollinear = preserve_collinear; - clipper.ReverseSolution = reverse_solution; + clipper.PreserveCollinear(preserve_collinear); + clipper.ReverseSolution(reverse_solution); if (sub.size() > 0) clipper.AddSubject(sub); if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); if (clp.size() > 0) clipper.AddClip(clp); @@ -466,8 +466,8 @@ EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype, PolyTree64 tree; Clipper64 clipper; - clipper.PreserveCollinear = preserve_collinear; - clipper.ReverseSolution = reverse_solution; + clipper.PreserveCollinear(preserve_collinear); + clipper.ReverseSolution(reverse_solution); if (sub.size() > 0) clipper.AddSubject(sub); if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); if (clp.size() > 0) clipper.AddClip(clp); diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index 14f978ed..9358b74b 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 1 November 2023 * +* Date : 22 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -1543,7 +1543,7 @@ namespace Clipper2Lib { //NB if preserveCollinear == true, then only remove 180 deg. spikes if ((CrossProduct(op2->prev->pt, op2->pt, op2->next->pt) == 0) && (op2->pt == op2->prev->pt || - op2->pt == op2->next->pt || !PreserveCollinear || + op2->pt == op2->next->pt || !preserve_collinear_ || DotProduct(op2->prev->pt, op2->pt, op2->next->pt) < 0)) { @@ -1708,6 +1708,28 @@ namespace Clipper2Lib { return op; } + inline void TrimHorz(Active& horzEdge, bool preserveCollinear) + { + bool wasTrimmed = false; + Point64 pt = NextVertex(horzEdge)->pt; + while (pt.y == horzEdge.top.y) + { + //always trim 180 deg. spikes (in closed paths) + //but otherwise break if preserveCollinear = true + if (preserveCollinear && + ((pt.x < horzEdge.top.x) != (horzEdge.bot.x < horzEdge.top.x))) + break; + + horzEdge.vertex_top = NextVertex(horzEdge); + horzEdge.top = pt; + wasTrimmed = true; + if (IsMaxima(horzEdge)) break; + pt = NextVertex(horzEdge)->pt; + } + + if (wasTrimmed) SetDx(horzEdge); // +/-infinity + } + inline void ClipperBase::UpdateEdgeIntoAEL(Active* e) { @@ -1719,9 +1741,13 @@ namespace Clipper2Lib { if (IsJoined(*e)) Split(*e, e->bot); - if (IsHorizontal(*e)) return; - InsertScanline(e->top.y); + if (IsHorizontal(*e)) + { + if (!IsOpen(*e)) TrimHorz(*e, preserve_collinear_); + return; + } + InsertScanline(e->top.y); CheckJoinLeft(*e, e->bot); CheckJoinRight(*e, e->bot, true); // (#500) } @@ -2453,35 +2479,6 @@ namespace Clipper2Lib { } } - inline bool HorzIsSpike(const Active& horzEdge) - { - Point64 nextPt = NextVertex(horzEdge)->pt; - return (nextPt.y == horzEdge.bot.y) && - (horzEdge.bot.x < horzEdge.top.x) != (horzEdge.top.x < nextPt.x); - } - - inline void TrimHorz(Active& horzEdge, bool preserveCollinear) - { - bool wasTrimmed = false; - Point64 pt = NextVertex(horzEdge)->pt; - while (pt.y == horzEdge.top.y) - { - //always trim 180 deg. spikes (in closed paths) - //but otherwise break if preserveCollinear = true - if (preserveCollinear && - ((pt.x < horzEdge.top.x) != (horzEdge.bot.x < horzEdge.top.x))) - break; - - horzEdge.vertex_top = NextVertex(horzEdge); - horzEdge.top = pt; - wasTrimmed = true; - if (IsMaxima(horzEdge)) break; - pt = NextVertex(horzEdge)->pt; - } - - if (wasTrimmed) SetDx(horzEdge); // +/-infinity - } - void ClipperBase::DoHorizontal(Active& horz) /******************************************************************************* * Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or * @@ -2507,10 +2504,10 @@ namespace Clipper2Lib { else vertex_max = GetCurrYMaximaVertex(horz); - // remove 180 deg.spikes and also simplify - // consecutive horizontals when PreserveCollinear = true - if (vertex_max && !horzIsOpen && vertex_max != horz.vertex_top) - TrimHorz(horz, PreserveCollinear); + //// remove 180 deg.spikes and also simplify + //// consecutive horizontals when PreserveCollinear = true + //if (!horzIsOpen && vertex_max != horz.vertex_top) + // TrimHorz(horz, PreserveCollinear); int64_t horz_left, horz_right; bool is_left_to_right = @@ -2539,6 +2536,9 @@ namespace Clipper2Lib { if (IsHotEdge(horz) && IsJoined(*e)) Split(*e, e->top); + //if (IsHotEdge(horz) != IsHotEdge(*e)) + // DoError(undefined_error_i); + if (IsHotEdge(horz)) { while (horz.vertex_top != vertex_max) @@ -2637,9 +2637,6 @@ namespace Clipper2Lib { AddOutPt(horz, horz.top); UpdateEdgeIntoAEL(&horz); - if (PreserveCollinear && !horzIsOpen && HorzIsSpike(horz)) - TrimHorz(horz, true); - is_left_to_right = ResetHorzDirection(horz, vertex_max, horz_left, horz_right); } @@ -2876,7 +2873,7 @@ namespace Clipper2Lib { if (!outrec->bounds.IsEmpty()) return true; CleanCollinear(outrec); if (!outrec->pts || - !BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)){ + !BuildPath64(outrec->pts, reverse_solution_, false, outrec->path)){ return false;} outrec->bounds = GetBounds(outrec->path); return true; @@ -2951,7 +2948,7 @@ namespace Clipper2Lib { Path64 path; if (solutionOpen && outrec->is_open) { - if (BuildPath64(outrec->pts, ReverseSolution, true, path)) + if (BuildPath64(outrec->pts, reverse_solution_, true, path)) solutionOpen->emplace_back(std::move(path)); } else @@ -2959,7 +2956,7 @@ namespace Clipper2Lib { // nb: CleanCollinear can add to outrec_list_ CleanCollinear(outrec); //closed paths should always return a Positive orientation - if (BuildPath64(outrec->pts, ReverseSolution, false, path)) + if (BuildPath64(outrec->pts, reverse_solution_, false, path)) solutionClosed.emplace_back(std::move(path)); } } @@ -2982,7 +2979,7 @@ namespace Clipper2Lib { if (outrec->is_open) { Path64 path; - if (BuildPath64(outrec->pts, ReverseSolution, true, path)) + if (BuildPath64(outrec->pts, reverse_solution_, true, path)) open_paths.push_back(path); continue; } @@ -3059,14 +3056,14 @@ namespace Clipper2Lib { PathD path; if (solutionOpen && outrec->is_open) { - if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_)) + if (BuildPathD(outrec->pts, reverse_solution_, true, path, invScale_)) solutionOpen->emplace_back(std::move(path)); } else { CleanCollinear(outrec); //closed paths should always return a Positive orientation - if (BuildPathD(outrec->pts, ReverseSolution, false, path, invScale_)) + if (BuildPathD(outrec->pts, reverse_solution_, false, path, invScale_)) solutionClosed.emplace_back(std::move(path)); } } @@ -3088,7 +3085,7 @@ namespace Clipper2Lib { if (outrec->is_open) { PathD path; - if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_)) + if (BuildPathD(outrec->pts, reverse_solution_, true, path, invScale_)) open_paths.push_back(path); continue; } diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index b8ee4481..2c7b167a 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 19 November 2023 * +* Date : 22 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -669,9 +669,9 @@ void ClipperOffset::Execute(double delta, Paths64& paths) bool paths_reversed = CheckReverseOrientation(); //clean up self-intersections ... Clipper64 c; - c.PreserveCollinear = false; + c.PreserveCollinear(false); //the solution should retain the orientation of the input - c.ReverseSolution = (reverse_solution_ != paths_reversed); + c.ReverseSolution(reverse_solution_ != paths_reversed); #ifdef USINGZ if (zCallback64_) { c.SetZCallback(zCallback64_); } #endif @@ -693,9 +693,9 @@ void ClipperOffset::Execute(double delta, PolyTree64& polytree) bool paths_reversed = CheckReverseOrientation(); //clean up self-intersections ... Clipper64 c; - c.PreserveCollinear = false; + c.PreserveCollinear(false); //the solution should retain the orientation of the input - c.ReverseSolution = reverse_solution_ != paths_reversed; + c.ReverseSolution (reverse_solution_ != paths_reversed); #ifdef USINGZ if (zCallback64_) { c.SetZCallback(zCallback64_); diff --git a/CPP/Tests/TestPolygons.cpp b/CPP/Tests/TestPolygons.cpp index 91746065..068dd318 100644 --- a/CPP/Tests/TestPolygons.cpp +++ b/CPP/Tests/TestPolygons.cpp @@ -112,5 +112,19 @@ TEST(Clipper2Tests, TestMultiplePolygons) Clipper2Lib::PathsD subjd, clipd, solutiond; Clipper2Lib::FillRule frd = Clipper2Lib::FillRule::NonZero; +} + +TEST(Clipper2Tests, TestHorzSpikes) //#720 +{ + Clipper2Lib::Paths64 paths = { + Clipper2Lib::MakePath({1600,0, 1600,100, 2050,100, 2050,300, 450,300, 450, 0}), + Clipper2Lib::MakePath({1800,200, 1800,100, 1600,100, 2000,100, 2000,200}) }; -} \ No newline at end of file + std::cout << paths << std::endl; + + Clipper2Lib::Clipper64 c; + c.AddSubject(paths); + c.Execute(Clipper2Lib::ClipType::Union, Clipper2Lib::FillRule::NonZero, paths); + + EXPECT_GE(paths.size(), 1); +} diff --git a/CPP/Tests/TestPolytreeUnion.cpp b/CPP/Tests/TestPolytreeUnion.cpp index ccda169d..4d53da87 100644 --- a/CPP/Tests/TestPolytreeUnion.cpp +++ b/CPP/Tests/TestPolytreeUnion.cpp @@ -20,7 +20,7 @@ TEST(Clipper2Tests, TestPolytreeUnion) { else { //because clipping ops normally return Positive solutions - clipper.ReverseSolution = true; + clipper.ReverseSolution(true); clipper.Execute(ClipType::Union, FillRule::Negative, solution, open_paths); } From 906bc1d505ed9ee641d29b164956896d5ae7356b Mon Sep 17 00:00:00 2001 From: angusj Date: Sat, 25 Nov 2023 15:09:24 +1000 Subject: [PATCH 89/95] ClipperOffset: fixed bug where rounding wasn't entirely accurate //#724 --- .../include/clipper2/clipper.core.h | 18 +- .../include/clipper2/clipper.engine.h | 8 +- CPP/Clipper2Lib/src/clipper.offset.cpp | 19 +- CPP/Tests/TestOffsets.cpp | 207 ++++++++++++++++-- CSharp/Clipper2Lib/Clipper.Engine.cs | 24 +- CSharp/Clipper2Lib/Clipper.Offset.cs | 22 +- Delphi/Clipper2Lib/Clipper.Engine.pas | 77 +++---- Delphi/Clipper2Lib/Clipper.Offset.pas | 21 +- 8 files changed, 292 insertions(+), 104 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.core.h b/CPP/Clipper2Lib/include/clipper2/clipper.core.h index e3e9824c..b3dddeea 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.core.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.core.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 22 November 2023 * +* Date : 24 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Core Clipper Library structures and functions * @@ -729,8 +729,9 @@ namespace Clipper2Lib } } - inline Point64 GetClosestPointOnSegment(const Point64& offPt, - const Point64& seg1, const Point64& seg2) + template + inline Point GetClosestPointOnSegment(const Point& offPt, + const Point& seg1, const Point& seg2) { if (seg1.x == seg2.x && seg1.y == seg2.y) return seg1; double dx = static_cast(seg2.x - seg1.x); @@ -740,9 +741,14 @@ namespace Clipper2Lib static_cast(offPt.y - seg1.y) * dy) / (Sqr(dx) + Sqr(dy)); if (q < 0) q = 0; else if (q > 1) q = 1; - return Point64( - seg1.x + static_cast(nearbyint(q * dx)), - seg1.y + static_cast(nearbyint(q * dy))); + if constexpr (std::numeric_limits::is_integer) + return Point( + seg1.x + static_cast(nearbyint(q * dx)), + seg1.y + static_cast(nearbyint(q * dy))); + else + return Point( + seg1.x + static_cast(q * dx), + seg1.y + static_cast(q * dy)); } enum class PointInPolygonResult { IsOn, IsInside, IsOutside }; diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h index cc41eb03..13c7f069 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.engine.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.engine.h @@ -282,11 +282,11 @@ namespace Clipper2Lib { void AddPaths(const Paths64& paths, PathType polytype, bool is_open); public: virtual ~ClipperBase(); - int ErrorCode() { return error_code_; }; + int ErrorCode() const { return error_code_; }; void PreserveCollinear(bool val) { preserve_collinear_ = val; }; - bool PreserveCollinear() { return preserve_collinear_;}; + bool PreserveCollinear() const { return preserve_collinear_;}; void ReverseSolution(bool val) { reverse_solution_ = val; }; - bool ReverseSolution() { return reverse_solution_; }; + bool ReverseSolution() const { return reverse_solution_; }; void Clear(); void AddReuseableData(const ReuseableDataContainer64& reuseable_data); #ifdef USINGZ @@ -419,7 +419,7 @@ namespace Clipper2Lib { PolyPathDList::const_iterator end() const { return childs_.cend(); } void SetScale(double value) { scale_ = value; } - double Scale() { return scale_; } + double Scale() const { return scale_; } PolyPathD* AddChild(const Path64& path) override { diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index 2c7b167a..d23c000f 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 22 November 2023 * +* Date : 25 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -411,7 +411,15 @@ void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size } else if (cos_a > 0.999) // almost straight - less than 2.5 degree (#424, #526) { - DoMiter(path, j, k, cos_a); + // with ::Round, preserving near exact delta is more important than simpler paths + // See also Issues #424, #526 #482 + if (join_type_ == JoinType::Round) + { + path_out.push_back(GetPerpendic(path[j], norms[k], group_delta_)); + path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_)); + } + else + DoMiter(path, j, k, cos_a); } else if (join_type_ == JoinType::Miter) { @@ -419,11 +427,12 @@ void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a); else DoSquare(path, j, k); } - else if (cos_a > 0.99 || join_type_ == JoinType::Bevel) - // ie > 2.5 deg (see above) but less than ~8 deg ( acos(0.99) ) - DoBevel(path, j, k); else if (join_type_ == JoinType::Round) DoRound(path, j, k, std::atan2(sin_a, cos_a)); + else if (/*cos_a > 0.99 || */ join_type_ == JoinType::Bevel) + // cos_a > 0.99 here improves performance with extremely minor reduction in accuracy + // acos(0.99) == 8.1 deg. still a small angle but not as small as cos_a > 0.999 (see above) + DoBevel(path, j, k); else DoSquare(path, j, k); } diff --git a/CPP/Tests/TestOffsets.cpp b/CPP/Tests/TestOffsets.cpp index 494a8da3..45a8c3e5 100644 --- a/CPP/Tests/TestOffsets.cpp +++ b/CPP/Tests/TestOffsets.cpp @@ -76,7 +76,7 @@ TEST(Clipper2Tests, TestOffsets2) { // see #448 & #456 if (d < delta * 2) { if (d < min_dist) min_dist = d; - if (d > max_dist) max_dist = d; + if (d> max_dist) max_dist = d; } prevPt = pt; } @@ -119,33 +119,29 @@ TEST(Clipper2Tests, TestOffsets4) // see #482 {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} }; Paths64 solution = InflatePaths(paths, -5000, JoinType::Square, EndType::Polygon); - std::cout << solution[0].size() << std::endl; - + //std::cout << solution[0].size() << std::endl; EXPECT_EQ(solution[0].size(), 5); paths = { { {0, 0}, {20000, 400}, {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} }; solution = InflatePaths(paths, -5000, JoinType::Square, EndType::Polygon); - std::cout << solution[0].size() << std::endl; - + //std::cout << solution[0].size() << std::endl; EXPECT_EQ(solution[0].size(), 5); paths = { { {0, 0}, {20000, 400}, {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} }; solution = InflatePaths(paths, -5000, - JoinType::Round, EndType::Polygon); - std::cout << solution[0].size() << std::endl; - - EXPECT_EQ(solution[0].size(), 5); + JoinType::Round, EndType::Polygon, 2, 100); + //std::cout << solution[0].size() << std::endl; + EXPECT_GT(solution[0].size(), 5); paths = { { {0, 0}, {20000, 1500}, {40000, 0}, {40000, 50000}, {0, 50000}, {0, 0}} }; solution = InflatePaths(paths, -5000, - JoinType::Round, EndType::Polygon); - std::cout << solution[0].size() << std::endl; - - EXPECT_GT(solution[0].size(), 6); + JoinType::Round, EndType::Polygon, 2, 100); + //std::cout << solution[0].size() << std::endl; + EXPECT_GT(solution[0].size(), 5); } TEST(Clipper2Tests, TestOffsets5) // modified from #593 (tests offset clean up) @@ -427,3 +423,188 @@ TEST(Clipper2Tests, TestOffsets7) // (#593 & #715) solution = InflatePaths(subject, -50, JoinType::Miter, EndType::Polygon); EXPECT_EQ(solution.size(), 0); } + +struct OffsetQual +{ + double smallestDist; + double largestDist; + size_t idxSmallestIn; //index to first segment pt + size_t idxSmallestOut; + size_t idxLargestIn; //index to first segment pt + size_t idxLargestOut; + double standardDev; +}; + +template +static OffsetQual GetOffsetQuality(const Path& input, const Path& output, const double desiredDist) +{ + if (!input.size() || !output.size()) return OffsetQual(); + + double desiredDistSqr = desiredDist * desiredDist; + double smallestSqr = desiredDistSqr, largestSqr = desiredDistSqr; + double deviationsSqr = 0; + size_t smallestInIdx = 0, largestInIdx = 0, smallestOutIdx = 0, largestOutIdx = 0; + size_t outIdx = 0; + for (const Point& outPt : output) + { + double closestDistSqr = std::numeric_limits::infinity(); + + size_t cpi = 0; //closest point index + Point in_prev = input[input.size() - 1]; + for (size_t i = 0; i < input.size(); ++i) + { + Point cp = Clipper2Lib::GetClosestPointOnSegment(outPt, input[i], in_prev); + in_prev = input[i]; + const double sqrDist = Clipper2Lib::DistanceSqr(cp, outPt); + if (sqrDist < closestDistSqr) { closestDistSqr = sqrDist; cpi = i; }; + } + + if (closestDistSqr < smallestSqr) + { + smallestSqr = closestDistSqr; + smallestInIdx = cpi; + smallestOutIdx = outIdx; + } + if (closestDistSqr > largestSqr) + { + largestSqr = closestDistSqr; + largestInIdx = cpi; + largestOutIdx = outIdx; + } + // we now have smallestDistSqr between outPt and the input path + double offset_qual = std::sqrt(closestDistSqr) - desiredDist; + deviationsSqr += offset_qual * offset_qual; + ++outIdx; + } + OffsetQual oq{}; + oq.smallestDist = std::sqrt(smallestSqr); + oq.largestDist = std::sqrt(largestSqr); + oq.idxSmallestIn = smallestInIdx == 0 ? input.size() - 1 : smallestInIdx - 1; + oq.idxLargestIn = largestInIdx == 0 ? input.size() - 1 : largestInIdx - 1; + oq.idxSmallestOut = smallestOutIdx; + oq.idxLargestOut = largestOutIdx; + oq.standardDev = std::sqrt(deviationsSqr / input.size()); + return oq; +} + +TEST(Clipper2Tests, TestOffsets8) // (#724) +{ + Paths64 subject = { MakePath({ + 91759700, -49711991, 83886095, -50331657, + -872415388, -50331657, -880288993, -49711991, -887968725, -47868251, + -895265482, -44845834, -901999593, -40719165, -908005244, -35589856, + -913134553, -29584205, -917261224, -22850094, -920283639, -15553337, + -922127379, -7873605, -922747045, 0, -922747045, 1434498600, + -922160557, 1442159790, -920414763, 1449642437, -917550346, 1456772156, + -913634061, 1463382794, -908757180, 1469320287, -903033355, 1474446264, + -896595982, 1478641262, -889595081, 1481807519, -882193810, 1483871245, + -876133965, 1484596521, -876145751, 1484713389, -875781839, 1485061090, + -874690056, 1485191762, -874447580, 1485237014, -874341490, 1485264094, + -874171960, 1485309394, -873612294, 1485570372, -873201878, 1485980788, + -872941042, 1486540152, -872893274, 1486720070, -872835064, 1487162210, + -872834788, 1487185500, -872769052, 1487406000, -872297948, 1487583168, + -871995958, 1487180514, -871995958, 1486914040, -871908872, 1486364208, + -871671308, 1485897962, -871301302, 1485527956, -870835066, 1485290396, + -870285226, 1485203310, -868659019, 1485203310, -868548443, 1485188472, + -868239649, 1484791011, -868239527, 1484783879, -838860950, 1484783879, + -830987345, 1484164215, -823307613, 1482320475, -816010856, 1479298059, + -809276745, 1475171390, -803271094, 1470042081, -752939437, 1419710424, + -747810128, 1413704773, -743683459, 1406970662, -740661042, 1399673904, + -738817302, 1391994173, -738197636, 1384120567, -738197636, 1244148246, + -738622462, 1237622613, -739889768, 1231207140, -802710260, 995094494, + -802599822, 995052810, -802411513, 994586048, -802820028, 993050638, + -802879992, 992592029, -802827240, 992175479, -802662144, 991759637, + -802578556, 991608039, -802511951, 991496499, -801973473, 990661435, + -801899365, 990554757, -801842657, 990478841, -801770997, 990326371, + -801946911, 989917545, -801636397, 989501855, -801546099, 989389271, + -800888669, 988625013, -800790843, 988518907, -800082405, 987801675, + -799977513, 987702547, -799221423, 987035738, -799109961, 986944060, + -798309801, 986330832, -798192297, 986247036, -797351857, 985690294, + -797228867, 985614778, -796352124, 985117160, -796224232, 985050280, + -795315342, 984614140, -795183152, 984556216, -794246418, 984183618, + -794110558, 984134924, -793150414, 983827634, -793011528, 983788398, + -792032522, 983547874, -791891266, 983518284, -790898035, 983345662, + -790755079, 983325856, -789752329, 983221956, -789608349, 983212030, + -787698545, 983146276, -787626385, 983145034, -536871008, 983145034, + -528997403, 982525368, -521317671, 980681627, -514020914, 977659211, + -507286803, 973532542, -501281152, 968403233, -496151843, 962397582, + -492025174, 955663471, -489002757, 948366714, -487159017, 940686982, + -486539351, 932813377, -486539351, 667455555, -486537885, 667377141, + -486460249, 665302309, -486448529, 665145917, -486325921, 664057737, + -486302547, 663902657, -486098961, 662826683, -486064063, 662673784, + -485780639, 661616030, -485734413, 661466168, -485372735, 660432552, + -485315439, 660286564, -484877531, 659282866, -484809485, 659141568, + -484297795, 658173402, -484219379, 658037584, -483636768, 657110363, + -483548422, 656980785, -482898150, 656099697, -482800368, 655977081, + -482086070, 655147053, -481979398, 655032087, -481205068, 654257759, + -481090104, 654151087, -480260074, 653436789, -480137460, 653339007, + -479256372, 652688735, -479126794, 652600389, -478199574, 652017779, + -478063753, 651939363, -477095589, 651427672, -476954289, 651359626, + -475950593, 650921718, -475804605, 650864422, -474770989, 650502744, + -474621127, 650456518, -473563373, 650173094, -473410475, 650138196, + -472334498, 649934610, -472179420, 649911236, -471091240, 649788626, + -470934848, 649776906, -468860016, 649699272, -468781602, 649697806, + -385876037, 649697806, -378002432, 649078140, -370322700, 647234400, + -363025943, 644211983, -356291832, 640085314, -350286181, 634956006, + -345156872, 628950354, -341030203, 622216243, -338007786, 614919486, + -336164046, 607239755, -335544380, 599366149, -335544380, 571247184, + -335426942, 571236100, -335124952, 570833446, -335124952, 569200164, + -335037864, 568650330, -334800300, 568184084, -334430294, 567814078, + -333964058, 567576517, -333414218, 567489431, -331787995, 567489431, + -331677419, 567474593, -331368625, 567077133, -331368503, 567070001, + -142068459, 567070001, -136247086, 566711605, -136220070, 566848475, + -135783414, 567098791, -135024220, 567004957, -134451560, 566929159, + -134217752, 566913755, -133983942, 566929159, -133411282, 567004957, + -132665482, 567097135, -132530294, 567091859, -132196038, 566715561, + -132195672, 566711157, -126367045, 567070001, -33554438, 567070001, + -27048611, 566647761, -20651940, 565388127, -14471751, 563312231, + -8611738, 560454902, 36793963, 534548454, 43059832, 530319881, + 48621743, 525200596, 53354240, 519306071, 57150572, 512769270, + 59925109, 505737634, 61615265, 498369779, 62182919, 490831896, + 62182919, 474237629, 62300359, 474226543, 62602349, 473823889, + 62602349, 472190590, 62689435, 471640752, 62926995, 471174516, + 63297005, 470804506, 63763241, 470566946, 64313081, 470479860, + 65939308, 470479860, 66049884, 470465022, 66358678, 470067562, + 66358800, 470060430, 134217752, 470060430, 134217752, 0, + 133598086, -7873605, 131754346, -15553337, 128731929, -22850094, + 124605260, -29584205, 119475951, -35589856, 113470300, -40719165, + 106736189, -44845834, 99439432, -47868251, 91759700, -49711991 + }) }; + + Paths64 solution; + ClipperOffset c; + double offset = -50329979.0, arc_tol = 2500.0; + + c.AddPaths(subject, JoinType::Round, EndType::Polygon); + c.ArcTolerance(arc_tol); + c.MiterLimit(2.0); + c.Execute(offset, solution); + + offset = std::abs(offset); + OffsetQual offset_qual = GetOffsetQuality(subject[0], solution[0], std::abs(offset)); + /* + std::cout.imbue(std::locale("")); + std::cout << std::setprecision(2) << std::fixed << std::setfill(' '); + std::cout << "Max dist. short of specified offset : " + << std::setw(12) << (offset - offset_qual.smallestDist) << std::endl; + std::cout << "Max dist. beyond specified offset : " + << std::setw(12) << (offset_qual.largestDist - offset) << std::endl; + + std::cout.imbue(std::locale("C")); //remove thousands separator + Point64 inPt = subject[0][offset_qual.idxSmallestIn]; + Point64 outPt = solution[0][offset_qual.idxSmallestOut]; + std::cout << "Distance less than delta" << std::endl; + std::cout << "Point in subject : " << inPt << std::endl; + std::cout << "Point in result : " << outPt << std::endl; + + inPt = subject[0][offset_qual.idxLargestIn]; + outPt = solution[0][offset_qual.idxLargestOut]; + std::cout << "Distance greater than delta" << std::endl; + std::cout << "Point in subject : " << inPt << std::endl; + std::cout << "Point in result : " << outPt << std::endl; + + std::cout << "StdDev of dist from specified offset : " + << std::setw(12) << offset_qual.standardDev << std::endl << std::endl; + */ + EXPECT_LE(std::abs(offset) - offset_qual.smallestDist , arc_tol); +} diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index 7834968f..eaee6a40 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 24 October 2023 * +* Date : 22 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -1527,7 +1527,11 @@ private void UpdateEdgeIntoAEL(Active ae) if (IsJoined(ae)) Split(ae, ae.bot); - if (IsHorizontal(ae)) return; + if (IsHorizontal(ae)) + { + if (!IsOpen(ae)) TrimHorz(ae, PreserveCollinear); + return; + } InsertScanline(ae.top.Y); CheckJoinLeft(ae, ae.bot); @@ -2078,13 +2082,6 @@ private static bool ResetHorzDirection(Active horz, Vertex? vertexMax, return false; // right to left } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool HorzIsSpike(Active horz) - { - Point64 nextPt = NextVertex(horz).pt; - return (horz.bot.X < horz.top.X) != (horz.top.X < nextPt.X); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void TrimHorz(Active horzEdge, bool preserveCollinear) { @@ -2147,12 +2144,6 @@ private void DoHorizontal(Active horz) GetCurrYMaximaVertex_Open(horz) : GetCurrYMaximaVertex(horz); - // remove 180 deg.spikes and also simplify - // consecutive horizontals when PreserveCollinear = true - if (vertex_max != null && - !horzIsOpen && vertex_max != horz.vertexTop) - TrimHorz(horz, PreserveCollinear); - bool isLeftToRight = ResetHorzDirection(horz, vertex_max, out long leftX, out long rightX); @@ -2271,9 +2262,6 @@ private void DoHorizontal(Active horz) UpdateEdgeIntoAEL(horz); - if (PreserveCollinear && !horzIsOpen && HorzIsSpike(horz)) - TrimHorz(horz, true); - isLeftToRight = ResetHorzDirection(horz, vertex_max, out leftX, out rightX); diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index b4fecb97..e798beb3 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 19 November 2023 * +* Date : 25 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -93,7 +93,6 @@ private static readonly string private readonly List _groupList = new List(); - private readonly Path64 inPath = new Path64(); private Path64 pathOut = new Path64(); private readonly PathD _normals = new PathD(); private readonly Paths64 _solution = new Paths64(); @@ -588,18 +587,29 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) pathOut.Add(GetPerpendic(path[j], _normals[j])); } else if (cosA > 0.999) - DoMiter(group, path, j, k, cosA); + { + // with ::Round, preserving near exact delta is more important than simpler paths + // See also Issues #424, #526 #482 + if (_joinType == JoinType.Round) + { + pathOut.Add(GetPerpendic(path[j], _normals[k])); + pathOut.Add(GetPerpendic(path[j], _normals[j])); + } + else + DoMiter(group, path, j, k, cosA); + } else if (_joinType == JoinType.Miter) { // miter unless the angle is so acute the miter would exceeds ML if (cosA > _mitLimSqr - 1) DoMiter(group, path, j, k, cosA); else DoSquare(path, j, k); } - else if (cosA > 0.99 || _joinType == JoinType.Bevel) - //angle less than 8 degrees or a squared join - DoBevel(path, j, k); else if (_joinType == JoinType.Round) DoRound(path, j, k, Math.Atan2(sinA, cosA)); + else if (/*cosA > 0.99 ||*/ _joinType == JoinType.Bevel) + // cos_a > 0.99 here improves performance with extremely minor reduction in accuracy + // acos(0.99) == 8.1 deg. still a small angle but not as small as cos_a > 0.999 (see above) + DoBevel(path, j, k); else DoSquare(path, j, k); diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index db5d6f85..da38f00a 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 24 October 2023 * +* Date : 22 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2480,6 +2480,31 @@ function TClipperBase.StartOpenPath(e: PActive; const pt: TPoint64): POutPt; end; //------------------------------------------------------------------------------ +procedure TrimHorz(horzEdge: PActive; preserveCollinear: Boolean); +var + pt: TPoint64; + wasTrimmed: Boolean; +begin + wasTrimmed := false; + pt := NextVertex(horzEdge).pt; + while (pt.Y = horzEdge.top.Y) do + begin + // always trim 180 deg. spikes (in closed paths) + // but otherwise break if preserveCollinear = true + if preserveCollinear and + ((pt.X < horzEdge.top.X) <> (horzEdge.bot.X < horzEdge.top.X)) then + break; + + horzEdge.vertTop := NextVertex(horzEdge); + horzEdge.top := pt; + wasTrimmed := true; + if IsMaxima(horzEdge) then Break; + pt := NextVertex(horzEdge).pt; + end; + if wasTrimmed then SetDx(horzEdge); // +/-infinity +end; +//------------------------------------------------------------------------------ + procedure TClipperBase.UpdateEdgeIntoAEL(var e: PActive); begin e.bot := e.top; @@ -2490,7 +2515,11 @@ procedure TClipperBase.UpdateEdgeIntoAEL(var e: PActive); if IsJoined(e) then UndoJoin(e, e.bot); - if IsHorizontal(e) then Exit; + if IsHorizontal(e) then + begin + if not IsOpen(e) then TrimHorz(e, PreserveCollinear); + Exit; + end; InsertScanLine(e.top.Y); CheckJoinLeft(e, e.bot); @@ -3333,41 +3362,6 @@ procedure TClipperBase.SwapPositionsInAEL(e1, e2: PActive); end; //------------------------------------------------------------------------------ -function HorzIsSpike(horzEdge: PActive): Boolean; -var - nextPt: TPoint64; -begin - nextPt := NextVertex(horzEdge).pt; - Result := (nextPt.Y = horzEdge.top.Y) and - (horzEdge.bot.X < horzEdge.top.X) <> (horzEdge.top.X < nextPt.X); -end; -//------------------------------------------------------------------------------ - -procedure TrimHorz(horzEdge: PActive; preserveCollinear: Boolean); -var - pt: TPoint64; - wasTrimmed: Boolean; -begin - wasTrimmed := false; - pt := NextVertex(horzEdge).pt; - while (pt.Y = horzEdge.top.Y) do - begin - // always trim 180 deg. spikes (in closed paths) - // but otherwise break if preserveCollinear = true - if preserveCollinear and - ((pt.X < horzEdge.top.X) <> (horzEdge.bot.X < horzEdge.top.X)) then - break; - - horzEdge.vertTop := NextVertex(horzEdge); - horzEdge.top := pt; - wasTrimmed := true; - if IsMaxima(horzEdge) then Break; - pt := NextVertex(horzEdge).pt; - end; - if wasTrimmed then SetDx(horzEdge); // +/-infinity -end; -//------------------------------------------------------------------------------ - function GetLastOp(hotEdge: PActive): POutPt; {$IFDEF INLINING} inline; {$ENDIF} var @@ -3444,10 +3438,6 @@ procedure TClipperBase.DoHorizontal(horzEdge: PActive); maxVertex := GetCurrYMaximaVertexOpen(horzEdge) else maxVertex := GetCurrYMaximaVertex(horzEdge); - if Assigned(maxVertex) and not horzIsOpen and - (maxVertex <> horzEdge.vertTop) then - TrimHorz(horzEdge, FPreserveCollinear); - isLeftToRight := ResetHorzDirection; // nb: TrimHorz above hence not using Bot.X here @@ -3569,11 +3559,6 @@ procedure TClipperBase.DoHorizontal(horzEdge: PActive); if IsHotEdge(horzEdge) then AddOutPt(horzEdge, horzEdge.top); UpdateEdgeIntoAEL(horzEdge); - - if PreserveCollinear and - not horzIsOpen and HorzIsSpike(horzEdge) then - TrimHorz(horzEdge, true); - isLeftToRight := ResetHorzDirection; end; // end while horizontal diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index 560822f2..5ba08a0f 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 19 November 2023 * +* Date : 25 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -1028,19 +1028,28 @@ procedure TClipperOffset.OffsetPoint(j: Integer; var k: integer); AddPoint(GetPerpendic(fInPath[j], fNorms[j], fGroupDelta)); end else if (cosA > 0.999) then - // almost straight - less than 2.5 degree (#424, #526) - DoMiter(j, k, cosA) + begin + // with ::Round, preserving near exact delta is more important + // than simpler paths (See also Issues #424, #526 #482) + if (fJoinType = jtRound) then + begin + AddPoint(GetPerpendic(fInPath[j], fNorms[k], fGroupDelta)); + AddPoint(GetPerpendic(fInPath[j], fNorms[j], fGroupDelta)); + end else + DoMiter(j, k, cosA) + end else if (fJoinType = jtMiter) then begin // miter unless the angle is so acute the miter would exceeds ML if (cosA > fTmpLimit -1) then DoMiter(j, k, cosA) else DoSquare(j, k); end - else if (cosA > 0.99) or (fJoinType = jtBevel) then - // ie > 2.5 deg (see above) but less than ~8 deg ( acos(0.99) ) - DoBevel(j, k) else if (fJoinType = jtRound) then DoRound(j, k, ArcTan2(sinA, cosA)) + else if {(cosA > 0.99) or} (fJoinType = jtBevel) then + // cos_a > 0.99 here improves performance with only very minor reductions in + // accuracy. acos(0.99) < ~8.1 deg. Not quite as small as cos_a > 0.999 above. + DoBevel(j, k) else DoSquare(j, k); From 88d0bc794b08e861ef7ea5e12e07a7bd2cd95b6a Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 26 Nov 2023 12:19:09 +1000 Subject: [PATCH 90/95] Additional minor bugfix to ClipperOffset (#724 & Disc.#726) --- CPP/Clipper2Lib/src/clipper.offset.cpp | 21 +-- CPP/Tests/TestOffsets.cpp | 175 +++++++++++++------------ CSharp/Clipper2Lib/Clipper.Offset.cs | 21 +-- Delphi/Clipper2Lib/Clipper.Offset.pas | 20 +-- 4 files changed, 111 insertions(+), 126 deletions(-) diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index d23c000f..48057016 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 25 November 2023 * +* Date : 26 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -409,29 +409,20 @@ void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size path_out.push_back(path[j]); // (#405) path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_)); } - else if (cos_a > 0.999) // almost straight - less than 2.5 degree (#424, #526) + else if (cos_a > 0.999 && join_type_ != JoinType::Round) { - // with ::Round, preserving near exact delta is more important than simpler paths - // See also Issues #424, #526 #482 - if (join_type_ == JoinType::Round) - { - path_out.push_back(GetPerpendic(path[j], norms[k], group_delta_)); - path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_)); - } - else - DoMiter(path, j, k, cos_a); + // almost straight - less than 2.5 degree (#424, #482, #526 & #724) + DoMiter(path, j, k, cos_a); } else if (join_type_ == JoinType::Miter) { - // miter unless the angle is so acute the miter would exceeds ML + // miter unless the angle is sufficiently acute to exceed ML if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a); else DoSquare(path, j, k); } else if (join_type_ == JoinType::Round) DoRound(path, j, k, std::atan2(sin_a, cos_a)); - else if (/*cos_a > 0.99 || */ join_type_ == JoinType::Bevel) - // cos_a > 0.99 here improves performance with extremely minor reduction in accuracy - // acos(0.99) == 8.1 deg. still a small angle but not as small as cos_a > 0.999 (see above) + else if ( join_type_ == JoinType::Bevel) DoBevel(path, j, k); else DoSquare(path, j, k); diff --git a/CPP/Tests/TestOffsets.cpp b/CPP/Tests/TestOffsets.cpp index 45a8c3e5..a37be1e8 100644 --- a/CPP/Tests/TestOffsets.cpp +++ b/CPP/Tests/TestOffsets.cpp @@ -1,7 +1,7 @@ #include #include "clipper2/clipper.offset.h" #include "ClipFileLoad.h" -#include +//#include "clipper.svg.utils.h" using namespace Clipper2Lib; TEST(Clipper2Tests, TestOffsets) { @@ -42,7 +42,7 @@ TEST(Clipper2Tests, TestOffsets) { } -Point64 MidPoint(const Point64& p1, const Point64& p2) +static Point64 MidPoint(const Point64& p1, const Point64& p2) { Point64 result; result.x = (p1.x + p2.x) / 2; @@ -424,66 +424,89 @@ TEST(Clipper2Tests, TestOffsets7) // (#593 & #715) EXPECT_EQ(solution.size(), 0); } + struct OffsetQual { - double smallestDist; - double largestDist; - size_t idxSmallestIn; //index to first segment pt - size_t idxSmallestOut; - size_t idxLargestIn; //index to first segment pt - size_t idxLargestOut; - double standardDev; + PointD smallestInSub; // smallestInSub & smallestInSol are the points in subject and solution + PointD smallestInSol; // that define the place that most falls short of the expected offset + PointD largestInSub; // largestInSub & largestInSol are the points in subject and solution + PointD largestInSol; // that define the place that most exceeds the expected offset }; + +template +inline PointD GetClosestPointOnSegment(const PointD& offPt, + const Point& seg1, const Point& seg2) +{ + if (seg1.x == seg2.x && seg1.y == seg2.y) return PointD(seg1); + double dx = static_cast(seg2.x - seg1.x); + double dy = static_cast(seg2.y - seg1.y); + double q = ( + (offPt.x - static_cast(seg1.x)) * dx + + (offPt.y - static_cast(seg1.y)) * dy) / + (Sqr(dx) + Sqr(dy)); + q = (q < 0) ? 0 : (q > 1) ? 1 : q; + return PointD( + static_cast(seg1.x) + (q * dx), + static_cast(seg1.y) + (q * dy)); +} + + template -static OffsetQual GetOffsetQuality(const Path& input, const Path& output, const double desiredDist) +static OffsetQual GetOffsetQuality(const Path& subject, const Path& solution, const double delta) { - if (!input.size() || !output.size()) return OffsetQual(); + if (!subject.size() || !solution.size()) return OffsetQual(); - double desiredDistSqr = desiredDist * desiredDist; + double desiredDistSqr = delta * delta; double smallestSqr = desiredDistSqr, largestSqr = desiredDistSqr; double deviationsSqr = 0; - size_t smallestInIdx = 0, largestInIdx = 0, smallestOutIdx = 0, largestOutIdx = 0; - size_t outIdx = 0; - for (const Point& outPt : output) - { - double closestDistSqr = std::numeric_limits::infinity(); + OffsetQual oq; - size_t cpi = 0; //closest point index - Point in_prev = input[input.size() - 1]; - for (size_t i = 0; i < input.size(); ++i) + const size_t subVertexCount = 4; // 1 .. 100 :) + const double subVertexFrac = 1.0 / subVertexCount; + Point outPrev = solution[solution.size() - 1]; + for (const Point& outPt : solution) + { + for (size_t i = 0; i < subVertexCount; ++i) { - Point cp = Clipper2Lib::GetClosestPointOnSegment(outPt, input[i], in_prev); - in_prev = input[i]; - const double sqrDist = Clipper2Lib::DistanceSqr(cp, outPt); - if (sqrDist < closestDistSqr) { closestDistSqr = sqrDist; cpi = i; }; - } + // divide each edge in solution into series of sub-vertices (solPt), + PointD solPt = PointD( + static_cast(outPrev.x) + static_cast(outPt.x - outPrev.x) * subVertexFrac * i, + static_cast(outPrev.y) + static_cast(outPt.y - outPrev.y) * subVertexFrac * i); + + // now find the closest point in subject to each of these solPt. + PointD closestToSolPt; + double closestDistSqr = std::numeric_limits::infinity(); + Point subPrev = subject[subject.size() - 1]; + for (size_t i = 0; i < subject.size(); ++i) + { + PointD closestPt = ::GetClosestPointOnSegment(solPt, subject[i], subPrev); + subPrev = subject[i]; + const double sqrDist = DistanceSqr(closestPt, solPt); + if (sqrDist < closestDistSqr) { + closestDistSqr = sqrDist; + closestToSolPt = closestPt; + }; + } - if (closestDistSqr < smallestSqr) - { - smallestSqr = closestDistSqr; - smallestInIdx = cpi; - smallestOutIdx = outIdx; - } - if (closestDistSqr > largestSqr) - { - largestSqr = closestDistSqr; - largestInIdx = cpi; - largestOutIdx = outIdx; + // we've now found solPt's closest pt in subject (closestToSolPt). + // but how does the distance between these 2 points compare with delta + // ideally - Distance(closestToSolPt, solPt) == delta; + + // see how this distance compares with every other solPt + if (closestDistSqr < smallestSqr) { + smallestSqr = closestDistSqr; + oq.smallestInSub = closestToSolPt; + oq.smallestInSol = solPt; + } + if (closestDistSqr > largestSqr) { + largestSqr = closestDistSqr; + oq.largestInSub = closestToSolPt; + oq.largestInSol = solPt; + } } - // we now have smallestDistSqr between outPt and the input path - double offset_qual = std::sqrt(closestDistSqr) - desiredDist; - deviationsSqr += offset_qual * offset_qual; - ++outIdx; + outPrev = outPt; } - OffsetQual oq{}; - oq.smallestDist = std::sqrt(smallestSqr); - oq.largestDist = std::sqrt(largestSqr); - oq.idxSmallestIn = smallestInIdx == 0 ? input.size() - 1 : smallestInIdx - 1; - oq.idxLargestIn = largestInIdx == 0 ? input.size() - 1 : largestInIdx - 1; - oq.idxSmallestOut = smallestOutIdx; - oq.idxLargestOut = largestOutIdx; - oq.standardDev = std::sqrt(deviationsSqr / input.size()); return oq; } @@ -571,40 +594,28 @@ TEST(Clipper2Tests, TestOffsets8) // (#724) 106736189, -44845834, 99439432, -47868251, 91759700, -49711991 }) }; - Paths64 solution; - ClipperOffset c; - double offset = -50329979.0, arc_tol = 2500.0; - - c.AddPaths(subject, JoinType::Round, EndType::Polygon); - c.ArcTolerance(arc_tol); - c.MiterLimit(2.0); - c.Execute(offset, solution); + double offset = -50329979.277800001, arc_tol = 5000; + Paths64 solution = InflatePaths(subject, offset, JoinType::Round, EndType::Polygon, 2, arc_tol); + OffsetQual oq = GetOffsetQuality(subject[0], solution[0], offset); + double smallestDist = Distance(oq.smallestInSub, oq.smallestInSol); + double largestDist = Distance(oq.largestInSub, oq.largestInSol); + const double rounding_tolerance = 1.0; offset = std::abs(offset); - OffsetQual offset_qual = GetOffsetQuality(subject[0], solution[0], std::abs(offset)); - /* - std::cout.imbue(std::locale("")); - std::cout << std::setprecision(2) << std::fixed << std::setfill(' '); - std::cout << "Max dist. short of specified offset : " - << std::setw(12) << (offset - offset_qual.smallestDist) << std::endl; - std::cout << "Max dist. beyond specified offset : " - << std::setw(12) << (offset_qual.largestDist - offset) << std::endl; - - std::cout.imbue(std::locale("C")); //remove thousands separator - Point64 inPt = subject[0][offset_qual.idxSmallestIn]; - Point64 outPt = solution[0][offset_qual.idxSmallestOut]; - std::cout << "Distance less than delta" << std::endl; - std::cout << "Point in subject : " << inPt << std::endl; - std::cout << "Point in result : " << outPt << std::endl; - - inPt = subject[0][offset_qual.idxLargestIn]; - outPt = solution[0][offset_qual.idxLargestOut]; - std::cout << "Distance greater than delta" << std::endl; - std::cout << "Point in subject : " << inPt << std::endl; - std::cout << "Point in result : " << outPt << std::endl; - - std::cout << "StdDev of dist from specified offset : " - << std::setw(12) << offset_qual.standardDev << std::endl << std::endl; - */ - EXPECT_LE(std::abs(offset) - offset_qual.smallestDist , arc_tol); + + //std::cout << std::setprecision(0) << std::fixed; + //std::cout << "Expected delta : " << offset << std::endl; + //std::cout << "Smallest delta : " << smallestDist << " (" << smallestDist - offset << ")" << std::endl; + //std::cout << "Largest delta : " << largestDist << " (" << largestDist - offset << ")" << std::endl; + //std::cout << "Coords of smallest delta : " << oq.smallestInSub << " and " << oq.smallestInSol << std::endl; + //std::cout << "Coords of largest delta : " << oq.largestInSub << " and " << oq.largestInSol << std::endl; + //std::cout << std::endl; + //SvgWriter svg; + //SvgAddSubject(svg, subject, FillRule::NonZero); + //SvgAddSolution(svg, solution, FillRule::NonZero, false); + //std::string filename = "offset_test.svg"; + //SvgSaveToFile(svg, filename, 800, 600, 10); + + EXPECT_LE(offset - smallestDist - rounding_tolerance, arc_tol); + EXPECT_LE(largestDist - offset - rounding_tolerance, arc_tol); } diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index e798beb3..fc519537 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 25 November 2023 * +* Date : 26 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -586,29 +586,20 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) pathOut.Add(path[j]); // (#405) pathOut.Add(GetPerpendic(path[j], _normals[j])); } - else if (cosA > 0.999) + else if ((cosA > 0.999) && (_joinType != JoinType.Round)) { - // with ::Round, preserving near exact delta is more important than simpler paths - // See also Issues #424, #526 #482 - if (_joinType == JoinType.Round) - { - pathOut.Add(GetPerpendic(path[j], _normals[k])); - pathOut.Add(GetPerpendic(path[j], _normals[j])); - } - else - DoMiter(group, path, j, k, cosA); + // almost straight - less than 2.5 degree (#424, #482, #526 & #724) + DoMiter(group, path, j, k, cosA); } else if (_joinType == JoinType.Miter) { - // miter unless the angle is so acute the miter would exceeds ML + // miter unless the angle is sufficiently acute to exceed ML if (cosA > _mitLimSqr - 1) DoMiter(group, path, j, k, cosA); else DoSquare(path, j, k); } else if (_joinType == JoinType.Round) DoRound(path, j, k, Math.Atan2(sinA, cosA)); - else if (/*cosA > 0.99 ||*/ _joinType == JoinType.Bevel) - // cos_a > 0.99 here improves performance with extremely minor reduction in accuracy - // acos(0.99) == 8.1 deg. still a small angle but not as small as cos_a > 0.999 (see above) + else if (_joinType == JoinType.Bevel) DoBevel(path, j, k); else DoSquare(path, j, k); diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index 5ba08a0f..fdab44e0 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 25 November 2023 * +* Date : 26 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -1027,28 +1027,20 @@ procedure TClipperOffset.OffsetPoint(j: Integer; var k: integer); AddPoint(fInPath[j]); // (#405) AddPoint(GetPerpendic(fInPath[j], fNorms[j], fGroupDelta)); end - else if (cosA > 0.999) then + else if (cosA > 0.999) and (fJoinType <> jtRound) then begin - // with ::Round, preserving near exact delta is more important - // than simpler paths (See also Issues #424, #526 #482) - if (fJoinType = jtRound) then - begin - AddPoint(GetPerpendic(fInPath[j], fNorms[k], fGroupDelta)); - AddPoint(GetPerpendic(fInPath[j], fNorms[j], fGroupDelta)); - end else - DoMiter(j, k, cosA) + // almost straight - less than 2.5 degree (#424, #482, #526 & #724) + DoMiter(j, k, cosA); end else if (fJoinType = jtMiter) then begin - // miter unless the angle is so acute the miter would exceeds ML + // miter unless the angle is sufficiently acute to exceed ML if (cosA > fTmpLimit -1) then DoMiter(j, k, cosA) else DoSquare(j, k); end else if (fJoinType = jtRound) then DoRound(j, k, ArcTan2(sinA, cosA)) - else if {(cosA > 0.99) or} (fJoinType = jtBevel) then - // cos_a > 0.99 here improves performance with only very minor reductions in - // accuracy. acos(0.99) < ~8.1 deg. Not quite as small as cos_a > 0.999 above. + else if (fJoinType = jtBevel) then DoBevel(j, k) else DoSquare(j, k); From ff858747b7fd7f0ceb0e97bb291031062c27c55c Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 26 Nov 2023 13:13:42 +1000 Subject: [PATCH 91/95] Version 1.2.4 Changes since ver 1.2.3 include: 1. Important: Offsetting open path behaviour has changed. The delta now behaves as it did in Clipper1 (See Issue #707) 2. Important: Data structures of exported values in clipper.export.h have changed. 3. Minor bugfixes to PolyTree nesting (#679, #687) 4. Numerous minor bugfixes to polygon offsetting (#703, #715, #724) 5. Fixed an obscure bug in polygon clipping caused by horizontal spikes (#720) 6. Significantly improved documentation. --- CPP/CMakeLists.txt | 2 +- CPP/Clipper2Lib/include/clipper2/clipper.version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CPP/CMakeLists.txt b/CPP/CMakeLists.txt index 9c419799..817cf017 100644 --- a/CPP/CMakeLists.txt +++ b/CPP/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.15) -project(Clipper2 VERSION 1.2.3 LANGUAGES C CXX) +project(Clipper2 VERSION 1.2.4 LANGUAGES C CXX) set(CMAKE_POSITION_INDEPENDENT_CODE ON) if(NOT DEFINED CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 17) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.version.h b/CPP/Clipper2Lib/include/clipper2/clipper.version.h index 638b222a..af39c3b5 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.2.3"; +constexpr auto CLIPPER2_VERSION = "1.2.4"; #endif // CLIPPER_VERSION_H From 0a6a3ab6a137c20edba28f6efc573edd752bc360 Mon Sep 17 00:00:00 2001 From: angusj Date: Sun, 26 Nov 2023 16:11:58 +1000 Subject: [PATCH 92/95] Fixed a typo and tweaked documentation --- CPP/Clipper2Lib/include/clipper2/clipper.export.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index dbaca63b..6115dd5b 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 22 November 2023 * +* Date : 26 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This module exports the Clipper2 Library (ie DLL/so) * @@ -21,10 +21,10 @@ 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 must be converted into simple C data structures that can be understood by just about any programming language. And these C style -path structures will be simple arrays of int94_t (CPath64) or double (CPathD). +path structures are simple arrays of int64_t (CPath64) and double (CPathD). CPath64 and CPathD: -These are simple arrays of consecutive x and y path coordinates preceeded by +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| @@ -32,7 +32,7 @@ __________________________________ __________________________________ CPaths64 and CPathsD: -These are also simple arrays containing any number of consecutive CPath64 or +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 (A) and the number of contained paths (C). From 41851ca1fba03051f29d360ffa0b973b5afd9bde Mon Sep 17 00:00:00 2001 From: angusj Date: Tue, 28 Nov 2023 22:43:36 +1000 Subject: [PATCH 93/95] Version 1.3.0 1. Fixed a significant offsetting bug introduced in previous update (#733) 2. Fixed a C++ compile error when 32bit compiling (#727) 3. Minor tweak to CMakeLists.txt (C++) (#728) 4. Will now offset 'flat' polygons (Disc.#725) 5. Reminder: recent change in open path offsetting behaviour (#707) 6. Reminder: recent changes to clipper.export.h data structures. --- CPP/BenchMark/CMakeLists.txt | 17 +- CPP/BenchMark/PointInPolygonBenchmark.cpp | 329 ++++++++++++++++++ CPP/BenchMark/StripDuplicateBenchmark.cpp | 2 +- CPP/CMakeLists.txt | 4 +- .../include/clipper2/clipper.export.h | 22 +- .../include/clipper2/clipper.version.h | 2 +- CPP/Clipper2Lib/src/clipper.offset.cpp | 42 ++- CPP/Tests/TestOffsets.cpp | 55 ++- CPP/Utils/Timer.h | 7 + CSharp/Clipper2Lib/Clipper.Offset.cs | 35 +- Delphi/Clipper2Lib/Clipper.Offset.pas | 45 ++- 11 files changed, 483 insertions(+), 77 deletions(-) create mode 100644 CPP/BenchMark/PointInPolygonBenchmark.cpp diff --git a/CPP/BenchMark/CMakeLists.txt b/CPP/BenchMark/CMakeLists.txt index 16a5389e..66cb9368 100644 --- a/CPP/BenchMark/CMakeLists.txt +++ b/CPP/BenchMark/CMakeLists.txt @@ -1,3 +1,12 @@ +cmake_minimum_required(VERSION 3.15) +project(Clipper2_benchmarks VERSION 1.0 LANGUAGES C CXX) + +if(NOT DEFINED CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 17) + set(CMAKE_CXX_STANDARD 17) +endif() +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + # fetch the google benchmark library include(FetchContent) set(BENCHMARK_ENABLE_GTEST_TESTS OFF) @@ -14,6 +23,7 @@ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) message("fetching is done") set(benchmark_srcs + PointInPolygonBenchmark.cpp StripDuplicateBenchmark.cpp # more to add ) @@ -24,13 +34,12 @@ foreach(benchmark ${benchmark_srcs}) message(STATUS "${PROJECT_NAME} add benchmark ${benchmark_target}") add_executable(${benchmark_target} ${benchmark}) - target_include_directories(${benchmark_target} PUBLIC - + target_include_directories(${benchmark_target} + PUBLIC ../Clipper2Lib/include + PUBLIC ../Utils ) target_link_libraries(${benchmark_target} benchmark::benchmark - Clipper2 - Clipper2utils ) endforeach() diff --git a/CPP/BenchMark/PointInPolygonBenchmark.cpp b/CPP/BenchMark/PointInPolygonBenchmark.cpp new file mode 100644 index 00000000..89a35fe2 --- /dev/null +++ b/CPP/BenchMark/PointInPolygonBenchmark.cpp @@ -0,0 +1,329 @@ +#include "benchmark/benchmark.h" +#include "clipper2/clipper.h" +#include "CommonUtils.h" +#include +#include + +using namespace Clipper2Lib; +using benchmark::State; + +template +inline PointInPolygonResult PIP1(const Point& pt, const Path& polygon) +{ + int val = 0; + typename Path::const_iterator cbegin = polygon.cbegin(), first = cbegin, curr, prev; + typename Path::const_iterator cend = polygon.cend(); + + while (first != cend && first->y == pt.y) ++first; + if (first == cend) // not a proper polygon + return PointInPolygonResult::IsOutside; + + bool is_above = first->y < pt.y, starting_above = is_above; + curr = first + 1; + while (true) + { + if (curr == cend) + { + if (cend == first || first == cbegin) break; + cend = first; + curr = cbegin; + } + + if (is_above) + { + while (curr != cend && curr->y < pt.y) ++curr; + if (curr == cend) continue; + } + else + { + while (curr != cend && curr->y > pt.y) ++curr; + if (curr == cend) continue; + } + + if (curr == cbegin) + prev = polygon.cend() - 1; + else + prev = curr - 1; + + if (curr->y == pt.y) + { + if (curr->x == pt.x || + (curr->y == prev->y && + ((pt.x < prev->x) != (pt.x < curr->x)))) + return PointInPolygonResult::IsOn; + ++curr; + if (curr == first) break; + continue; + } + + if (pt.x < curr->x && pt.x < prev->x) + { + // we're only interested in edges crossing on the left + } + else if (pt.x > prev->x && pt.x > curr->x) + val = 1 - val; // toggle val + else + { + double d = CrossProduct(*prev, *curr, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + is_above = !is_above; + ++curr; + } + + if (is_above != starting_above) + { + cend = polygon.cend(); + if (curr == cend) curr = cbegin; + if (curr == cbegin) prev = cend - 1; + else prev = curr - 1; + double d = CrossProduct(*prev, *curr, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + + return (val == 0) ? + PointInPolygonResult::IsOutside : + PointInPolygonResult::IsInside; +} + + +template +inline PointInPolygonResult PIP2(const Point& pt, const Path& polygon) +{ + if (!polygon.size()) return PointInPolygonResult::IsOutside; + Path::const_iterator cend = polygon.cend(); + Path::const_iterator prev = cend - 1; + Path::const_iterator curr = polygon.cbegin(); + + bool is_above; + if (prev->y == pt.y) + { + if (pt == *prev) return PointInPolygonResult::IsOn; + if ((curr->y == pt.y) && ((curr->x == pt.x) || + ((pt.x > prev->x) == (pt.x < curr->x)))) + return PointInPolygonResult::IsOn; + Path::const_reverse_iterator pr = polygon.crbegin() +1; + while (pr != polygon.crend() && pr->y == pt.y) ++pr; + is_above = pr == polygon.crend() || pr->y < pt.y; + } + else is_above = prev->y < pt.y; + + int val = 0; + while (curr != cend) + { + if (is_above) + { + while (curr != cend && curr->y < pt.y) { prev = curr; ++curr; } + if (curr == cend) break; + } + else + { + while (curr != cend && curr->y > pt.y) { prev = curr; ++curr; } + if (curr == cend) break; + } + + if (curr->y == pt.y) + { + if ((curr->x == pt.x) || ((curr->y == prev->y) && + ((pt.x > prev->x) == (pt.x < curr->x)))) + return PointInPolygonResult::IsOn; + prev = curr; + ++curr; + continue; + } + + if (pt.x < curr->x && pt.x < prev->x) + { + // we're only interested in edges crossing on the left + } + else if (pt.x > prev->x && pt.x > curr->x) + ++val; + else + { + double d = CrossProduct(*prev, *curr, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) ++val; + } + is_above = !is_above; + prev = curr; + ++curr; + } + + return (val % 2) ? PointInPolygonResult::IsInside : PointInPolygonResult::IsOutside; +} + + +// "Optimal Reliable Point-in-Polygon Test and +// Differential Coding Boolean Operations on Polygons" +// by Jianqiang Hao et al. +// Symmetry 2018, 10(10), 477; https://doi.org/10.3390/sym10100477 +template +static PointInPolygonResult PIP3(const Point &pt, const Path &path) +{ + T x1, y1, x2, y2; + int k = 0; + Path::const_iterator itPrev = path.cend() - 1; + Path::const_iterator itCurr = path.cbegin(); + for ( ; itCurr != path.cend(); ++itCurr) + { + y1 = itPrev->y - pt.y; + y2 = itCurr->y - pt.y; + if (((y1 < 0) && (y2 < 0)) || ((y1 > 0) && (y2 > 0))) + { + itPrev = itCurr; + continue; + } + + x1 = itPrev->x - pt.x; + x2 = itCurr->x - pt.x; + if ((y1 <= 0) && (y2 > 0)) + { + //double f = double(x1) * y2 - double(x2) * y1; // avoids int overflow + int64_t f = x1 * y2 - x2 * y1; + if (f > 0) ++k; + else if (f == 0) return PointInPolygonResult::IsOn; + } + else if ((y1 > 0) && (y2 <= 0)) + { + int64_t f = x1 * y2 - x2 * y1; + if (f < 0) ++k; + else if (f == 0) return PointInPolygonResult::IsOn; + } + else if (((y2 == 0) && (y1 < 0)) || ((y1 == 0) && (y2 < 0))) + { + int64_t f = x1 * y2 - x2 * y1; + if (f == 0) return PointInPolygonResult::IsOn; + } + else if ((y1 == 0) && (y2 == 0) && + (((x2 <= 0) && (x1 >= 0)) || ((x1 <= 0) && (x2 >= 0)))) + return PointInPolygonResult::IsOn; + itPrev = itCurr; + } + if (k % 2) return PointInPolygonResult::IsInside; + return PointInPolygonResult::IsOutside; +} + + +Paths64 paths; +Point64 mp; +PointInPolygonResult pip1 = PointInPolygonResult::IsOn; +PointInPolygonResult pip2 = PointInPolygonResult::IsOn; +PointInPolygonResult pip3 = PointInPolygonResult::IsOn; + + +static void BM_PIP1(benchmark::State& state) +{ + for (auto _ : state) + { + pip1 = PIP1(mp, paths[state.range(0)]); + } +} + +static void BM_PIP2(benchmark::State& state) +{ + for (auto _ : state) + { + pip2 = PIP2(mp, paths[state.range(0)]); + } +} + +static void BM_PIP3(benchmark::State& state) +{ + for (auto _ : state) + { + pip3 = PIP3(mp, paths[state.range(0)]); + } +} + +static void CustomArguments(benchmark::internal::Benchmark* b) +{ + for (int i = 0; i < paths.size(); ++i) b->Args({ i }); +} + +enum DoTests { do_stress_test_only, do_benchmark_only, do_all_tests }; + +int main(int argc, char** argv) { + + const DoTests do_tests = do_all_tests; + + if (do_tests != do_benchmark_only) + { + // stress test PIP2 with unusual polygons + mp = Point64(10, 10); + std::vector pipResults; + + paths.push_back({}); + pipResults.push_back(PointInPolygonResult::IsOutside); + paths.push_back(MakePath({ 100,10, 200,10 })); + pipResults.push_back(PointInPolygonResult::IsOutside); + paths.push_back(MakePath({ 100,10, 200,10, 10,10, 20,20 })); + pipResults.push_back(PointInPolygonResult::IsOn); + paths.push_back(MakePath({ 10,10 })); + pipResults.push_back(PointInPolygonResult::IsOn); + paths.push_back(MakePath({ 100,10 })); + pipResults.push_back(PointInPolygonResult::IsOutside); + paths.push_back(MakePath({ 100,10, 110,20, 200,10, 10,10, 20,20 })); + pipResults.push_back(PointInPolygonResult::IsOn); + paths.push_back(MakePath({ 100,10, 110,20, 200,10, 20,20 })); + pipResults.push_back(PointInPolygonResult::IsOutside); + paths.push_back(MakePath({ 200,0, 0,0, 10,20, 200,0, 20,0 })); + pipResults.push_back(PointInPolygonResult::IsInside); + paths.push_back(MakePath({ 0,0, 20,20, 100,0 })); + pipResults.push_back(PointInPolygonResult::IsOn); + + std::cout << "Stress testing PIP1 for errors: "; + for (size_t i = 0; i < paths.size(); ++i) + if (PIP1(mp, paths[i]) != pipResults[i]) + std::cout << " (" << i << ")"; + std::cout << std::endl; + std::cout << "Stress testing PIP2 for errors: "; + for (size_t i = 0; i < paths.size(); ++i) + if (PIP2(mp, paths[i]) != pipResults[i]) + std::cout << " (" << i << ")"; + std::cout << std::endl; + std::cout << "Stress testing PIP3 for errors: "; + for (size_t i = 0; i < paths.size(); ++i) + if (PIP3(mp, paths[i]) != pipResults[i]) + std::cout << " (" << i << ")"; + std::cout << std::endl << std::endl; + + if (do_tests != do_all_tests) + { + std::string _; + std::getline(std::cin, _); + return 0; + } + } + + if (do_tests == do_stress_test_only) return 0; + + // compare 3 PIP algorithms + const int width = 600000, height = 400000; + mp = Point64(width / 2, height / 2); + paths.clear(); + srand((unsigned)time(0)); + for (int i = 0, count = 10000; i < 5; ++i, count *= 10) + paths.push_back(MakeRandomPoly(width, height, count)); + + benchmark::Initialize(&argc, argv); + BENCHMARK(BM_PIP1)->Apply(CustomArguments); // current Clipper2 + BENCHMARK(BM_PIP2)->Apply(CustomArguments); // modified Clipper2 + BENCHMARK(BM_PIP3)->Apply(CustomArguments); // Hao et al. (2018) + benchmark::RunSpecifiedBenchmarks(); + + if (pip2 != pip1 || pip3 != pip1) + { + if (pip2 != pip1) + std::cout << "PIP2 result is wrong!!!"; + else + std::cout << "PIP3 result is wrong!!!"; + std::cout << paths[2] << std::endl << std::endl; + std::string _; + std::getline(std::cin, _); + return 1; + } + + return 0; +} diff --git a/CPP/BenchMark/StripDuplicateBenchmark.cpp b/CPP/BenchMark/StripDuplicateBenchmark.cpp index d44b89c8..92c07196 100644 --- a/CPP/BenchMark/StripDuplicateBenchmark.cpp +++ b/CPP/BenchMark/StripDuplicateBenchmark.cpp @@ -1,6 +1,6 @@ #include "benchmark/benchmark.h" #include "clipper2/clipper.h" -#include "Utils/CommonUtils.h" +#include "CommonUtils.h" #include static void CustomArguments(benchmark::internal::Benchmark *b) { diff --git a/CPP/CMakeLists.txt b/CPP/CMakeLists.txt index 817cf017..98d8ec49 100644 --- a/CPP/CMakeLists.txt +++ b/CPP/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.15) -project(Clipper2 VERSION 1.2.4 LANGUAGES C CXX) +project(Clipper2 VERSION 1.3.0 LANGUAGES C CXX) set(CMAKE_POSITION_INDEPENDENT_CODE ON) if(NOT DEFINED CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 17) @@ -19,7 +19,7 @@ set(CLIPPER2_USINGZ "ON" CACHE STRING "Build Clipper2Z, either \"ON\" or \"OFF\" set(CLIPPER2_MAX_PRECISION 8 CACHE STRING "Maximum precision allowed for double to int64 scaling") if (APPLE) - set(CMAKE_SHARED_LIBRARY_SUFFIX ".so") + set(CMAKE_SHARED_LIBRARY_SUFFIX ".dylib") endif () include(GNUInstallDirs) diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.export.h b/CPP/Clipper2Lib/include/clipper2/clipper.export.h index 6115dd5b..d7286132 100644 --- a/CPP/Clipper2Lib/include/clipper2/clipper.export.h +++ b/CPP/Clipper2Lib/include/clipper2/clipper.export.h @@ -34,17 +34,17 @@ __________________________________ 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 (A) and the number of -contained paths (C). +values that contain the total length of the array (A) structure and +the number (C) of CPath64 or CPathD it contains. _______________________________ |counter|path1|path2|...|pathC| |A , C | | _______________________________ CPolytree64 and CPolytreeD: -These are also simple arrays consisting of CPolyPath structures that -represent individual paths in a tree structure. However, the very first -CPolyPath is just the tree container so it won't have a path. And because +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 won'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. @@ -59,18 +59,18 @@ ____________________________________________________________ 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 first -value will contain the total length of the CPolytree array. +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 structure. -All the exported structures (CPaths64, CPathsD, CPolyTree64 & CPolyTreeD) +All theses exported structures (CPaths64, CPathsD, CPolyTree64 & CPolyTreeD) are arrays of type int64_t or double. And the first value in these arrays will always contain the length of that array. These array structures are allocated in heap memory which will eventually need to be released. But since applications dynamically linking to these -functions may use different memory managers, the only really safe way to -free up this memory is to use the exported DisposeArray64 and -DisposeArrayD functions below. +functions may use different memory managers, the only safe way to free up +this memory is to use the exported DisposeArray64 and DisposeArrayD +functions below. */ diff --git a/CPP/Clipper2Lib/include/clipper2/clipper.version.h b/CPP/Clipper2Lib/include/clipper2/clipper.version.h index af39c3b5..d7644067 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.2.4"; +constexpr auto CLIPPER2_VERSION = "1.3.0"; #endif // CLIPPER_VERSION_H diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index 48057016..514fbc8c 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 November 2023 * +* Date : 28 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -20,13 +20,17 @@ const double floating_point_tolerance = 1e-12; // Miscellaneous methods //------------------------------------------------------------------------------ -void GetMultiBounds(const Paths64& paths, std::vector& recList, EndType end_type) +inline bool ToggleBoolIf(bool val, bool condition) +{ + return condition ? !val : val; +} + +void GetMultiBounds(const Paths64& paths, std::vector& recList) { - size_t min_path_len = (end_type == EndType::Polygon) ? 3 : 1; recList.reserve(paths.size()); for (const Path64& path : paths) { - if (path.size() < min_path_len) + if (path.size() < 1) { recList.push_back(InvalidRect64); continue; @@ -161,7 +165,7 @@ ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType StripDuplicates(p, is_joined); // get bounds of each path --> bounds_list - GetMultiBounds(paths_in, bounds_list, end_type); + GetMultiBounds(paths_in, bounds_list); if (end_type == EndType::Polygon) { @@ -206,10 +210,10 @@ void ClipperOffset::BuildNormals(const Path64& path) norms.clear(); norms.reserve(path.size()); if (path.size() == 0) return; - Path64::const_iterator path_iter, path_last_iter = --path.cend(); - for (path_iter = path.cbegin(); path_iter != path_last_iter; ++path_iter) + 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_last_iter, *(path.cbegin()))); + norms.push_back(GetUnitNormal(*path_stop_iter, *(path.cbegin()))); } inline PointD TranslatePoint(const PointD& pt, double dx, double dy) @@ -515,8 +519,9 @@ void ClipperOffset::DoGroupOffset(Group& group) { if (group.end_type == EndType::Polygon) { - if (group.lowest_path_idx < 0) return; - //if (area == 0) return; // probably unhelpful (#430) + // 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_); group_delta_ = (group.is_reversed) ? -delta_ : delta_; } else @@ -535,7 +540,7 @@ void ClipperOffset::DoGroupOffset(Group& group) if (group.join_type == JoinType::Round || group.end_type == EndType::Round) { - //calculate a sensible number of steps (for 360 deg for the given offset) + // calculate a sensible number of steps (for 360 deg for the given offset) // arcTol - when arc_tolerance_ is undefined (0), the amount of // curve imprecision that's allowed is based on the size of the // offset (delta). Obviously very large offsets will almost always @@ -551,7 +556,6 @@ void ClipperOffset::DoGroupOffset(Group& group) steps_per_rad_ = steps_per_360 / (2 * PI); } - std::vector::const_iterator path_rect_it = group.bounds_list.cbegin(); std::vector::const_iterator is_hole_it = group.is_hole_list.cbegin(); Paths64::const_iterator path_in_it = group.paths_in.cbegin(); @@ -561,7 +565,7 @@ void ClipperOffset::DoGroupOffset(Group& group) Path64::size_type pathLen = path_in_it->size(); path_out.clear(); - if (pathLen == 1) // single point - only valid with open paths + if (pathLen == 1) // single point { if (group_delta_ < 1) continue; const Point64& pt = (*path_in_it)[0]; @@ -586,12 +590,12 @@ void ClipperOffset::DoGroupOffset(Group& group) } solution.push_back(path_out); continue; - } // end of offsetting a single (open path) point + } // end of offsetting a single point - // when shrinking, then make sure the path can shrink that far (#593) - // but also make sure this isn't a hole which will be expanding (#715) - if ( ((group_delta_ < 0) != *is_hole_it) && - (std::min(path_rect_it->Width(), path_rect_it->Height()) < -group_delta_ * 2) ) + // when shrinking outer paths, make sure they can shrink this far (#593) + // also when shrinking holes, make sure they too can shrink this far (#715) + if (group_delta_ > 0 == ToggleBoolIf(*is_hole_it, group.is_reversed) && + (std::min(path_rect_it->Width(), path_rect_it->Height()) <= -group_delta_ * 2) ) continue; if ((pathLen == 2) && (group.end_type == EndType::Joined)) @@ -637,7 +641,7 @@ void ClipperOffset::ExecuteInternal(double delta) if (std::abs(delta) < 0.5) // ie: offset is insignificant { - int64_t sol_size = 0; + Paths64::size_type sol_size = 0; for (const Group& group : groups_) sol_size += group.paths_in.size(); solution.reserve(sol_size); for (const Group& group : groups_) diff --git a/CPP/Tests/TestOffsets.cpp b/CPP/Tests/TestOffsets.cpp index a37be1e8..d5805e65 100644 --- a/CPP/Tests/TestOffsets.cpp +++ b/CPP/Tests/TestOffsets.cpp @@ -464,15 +464,15 @@ static OffsetQual GetOffsetQuality(const Path& subject, const Path& soluti const size_t subVertexCount = 4; // 1 .. 100 :) const double subVertexFrac = 1.0 / subVertexCount; - Point outPrev = solution[solution.size() - 1]; - for (const Point& outPt : solution) + Point solPrev = solution[solution.size() - 1]; + for (const Point& solPt0 : solution) { for (size_t i = 0; i < subVertexCount; ++i) { // divide each edge in solution into series of sub-vertices (solPt), PointD solPt = PointD( - static_cast(outPrev.x) + static_cast(outPt.x - outPrev.x) * subVertexFrac * i, - static_cast(outPrev.y) + static_cast(outPt.y - outPrev.y) * subVertexFrac * i); + static_cast(solPrev.x) + static_cast(solPt0.x - solPrev.x) * subVertexFrac * i, + static_cast(solPrev.y) + static_cast(solPt0.y - solPrev.y) * subVertexFrac * i); // now find the closest point in subject to each of these solPt. PointD closestToSolPt; @@ -505,7 +505,7 @@ static OffsetQual GetOffsetQuality(const Path& subject, const Path& soluti oq.largestInSol = solPt; } } - outPrev = outPt; + solPrev = solPt0; } return oq; } @@ -619,3 +619,48 @@ TEST(Clipper2Tests, TestOffsets8) // (#724) EXPECT_LE(offset - smallestDist - rounding_tolerance, arc_tol); EXPECT_LE(largestDist - offset - rounding_tolerance, arc_tol); } + + +TEST(Clipper2Tests, TestOffsets9) // (#733) +{ + // solution orientations should match subject orientations UNLESS + // reverse_solution is set true in ClipperOffset's constructor + + // start subject's orientation positive ... + Paths64 subject{ MakePath({100,100, 200,100, 200, 400, 100, 400}) }; + Paths64 solution = InflatePaths(subject, 50, JoinType::Miter, EndType::Polygon); + EXPECT_EQ(solution.size(), 1); + EXPECT_TRUE(IsPositive(solution[0])); + + // reversing subject's orientation should not affect delta direction + // (ie where positive deltas inflate). + std::reverse(subject[0].begin(), subject[0].end()); + solution = InflatePaths(subject, 50, JoinType::Miter, EndType::Polygon); + EXPECT_EQ(solution.size(), 1); + EXPECT_TRUE(std::fabs(Area(solution[0])) > std::fabs(Area(subject[0]))); + EXPECT_FALSE(IsPositive(solution[0])); + + ClipperOffset co(2, 0, false, true); // last param. reverses solution + co.AddPaths(subject, JoinType::Miter, EndType::Polygon); + co.Execute(50, solution); + EXPECT_EQ(solution.size(), 1); + EXPECT_TRUE(std::fabs(Area(solution[0])) > std::fabs(Area(subject[0]))); + EXPECT_TRUE(IsPositive(solution[0])); + + // add a hole (ie has reverse orientation to outer path) + subject.push_back( MakePath({130,130, 170,130, 170,370, 130,370}) ); + solution = InflatePaths(subject, 30, JoinType::Miter, EndType::Polygon); + EXPECT_EQ(solution.size(), 1); + EXPECT_FALSE(IsPositive(solution[0])); + + co.Clear(); // should still reverse solution orientation + co.AddPaths(subject, JoinType::Miter, EndType::Polygon); + co.Execute(30, solution); + EXPECT_EQ(solution.size(), 1); + EXPECT_TRUE(std::fabs(Area(solution[0])) > std::fabs(Area(subject[0]))); + EXPECT_TRUE(IsPositive(solution[0])); + + solution = InflatePaths(subject, -15, JoinType::Miter, EndType::Polygon); + EXPECT_EQ(solution.size(), 0); + +} diff --git a/CPP/Utils/Timer.h b/CPP/Utils/Timer.h index 3f1e8a7c..c8994c29 100644 --- a/CPP/Utils/Timer.h +++ b/CPP/Utils/Timer.h @@ -44,6 +44,13 @@ struct Timer { std::chrono::high_resolution_clock::now(); } + void restart() + { + paused_ = false; + duration_ = {}; + time_started_ = std::chrono::high_resolution_clock::now(); + } + void resume() { if (!paused_) return; diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index fc519537..6c95210d 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 26 November 2023 * +* Date : 28 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -55,7 +55,7 @@ public Group(Paths64 paths, JoinType joinType, EndType endType = EndType.Polygon // get bounds of each path --> boundsList boundsList = new List(inPaths.Count); - GetMultiBounds(inPaths, boundsList, endType); + GetMultiBounds(inPaths, boundsList); if (endType == EndType.Polygon) { @@ -255,14 +255,12 @@ public void Execute(DeltaCallback64 deltaCallback, Paths64 solution) Execute(1.0, solution); } - internal static void GetMultiBounds(Paths64 paths, List boundsList, EndType endType) + internal static void GetMultiBounds(Paths64 paths, List boundsList) { - - int minPathLen = (endType == EndType.Polygon) ? 3 : 1; boundsList.Capacity = paths.Count; foreach (Path64 path in paths) { - if (path.Count < minPathLen) + if (path.Count < 1) { boundsList.Add(InvalidRect64); continue; @@ -686,16 +684,22 @@ private void OffsetOpenPath(Group group, Path64 path) _solution.Add(pathOut); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool ToggleBoolIf(bool val, bool condition) + { + return condition ? !val : val; + } private void DoGroupOffset(Group group) { if (group.endType == EndType.Polygon) { - if (group.lowestPathIdx < 0) return; - //if (area == 0) return; // probably unhelpful (#430) + // a straight path (2 points) can now also be 'polygon' offset + // where the ends will be treated as (180 deg.) joins + if (group.lowestPathIdx < 0) _delta = Math.Abs(_delta); _groupDelta = (group.pathsReversed) ? -_delta : _delta; } else - _groupDelta = Math.Abs(_delta);// * 0.5; + _groupDelta = Math.Abs(_delta); double absDelta = Math.Abs(_groupDelta); if (!ValidateBounds(group.boundsList, absDelta)) @@ -760,13 +764,14 @@ private void DoGroupOffset(Group group) } _solution.Add(pathOut); continue; - } // end of offsetting a single (open path) point + } // end of offsetting a single point - // when shrinking, then make sure the path can shrink that far (#593) - // but also make sure this isn't a hole which will be expanding (#715) - if (((_groupDelta < 0) != isHole) && - (Math.Min(pathBounds.Width, pathBounds.Height) < -_groupDelta * 2)) - continue; + + // when shrinking outer paths, make sure they can shrink this far (#593) + // also when shrinking holes, make sure they too can shrink this far (#715) + if (((_groupDelta > 0) == ToggleBoolIf(isHole, group.pathsReversed)) && + (Math.Min(pathBounds.Width, pathBounds.Height) <= -_groupDelta * 2)) + continue; if (cnt == 2 && group.endType == EndType.Joined) _endType = (group.joinType == JoinType.Round) ? diff --git a/Delphi/Clipper2Lib/Clipper.Offset.pas b/Delphi/Clipper2Lib/Clipper.Offset.pas index fdab44e0..0aa35138 100644 --- a/Delphi/Clipper2Lib/Clipper.Offset.pas +++ b/Delphi/Clipper2Lib/Clipper.Offset.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 26 November 2023 * +* Date : 28 November 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : Path Offset (Inflate/Shrink) * @@ -141,22 +141,19 @@ implementation // Miscellaneous offset support functions //------------------------------------------------------------------------------ -procedure GetMultiBounds(const paths: TPaths64; var list: TRect64Array; endType: TEndType); +procedure GetMultiBounds(const paths: TPaths64; var list: TRect64Array); var - i,j, len, len2, minPathLen: integer; + i,j, len, len2: integer; path: TPath64; pt1, pt: TPoint64; r: TRect64; begin - if endType = etPolygon then - minPathLen := 3 else - minPathLen := 1; len := Length(paths); for i := 0 to len -1 do begin path := paths[i]; len2 := Length(path); - if len2 < minPathLen then + if len2 < 1 then begin list[i] := InvalidRect64; continue; @@ -323,7 +320,7 @@ constructor TGroup.Create(const pathsIn: TPaths64; jt: TJoinType; et: TEndType); reversed := false; SetLength(isHoleList, len); SetLength(boundsList, len); - GetMultiBounds(paths, boundsList, et); + GetMultiBounds(paths, boundsList); if (et = etPolygon) then begin lowestPathIdx := GetLowestClosedPathIdx(boundsList); @@ -426,6 +423,15 @@ function GetPerpendicD(const pt: TPoint64; const norm: TPointD; delta: double): end; //------------------------------------------------------------------------------ +function ToggleBoolIf(val, condition: Boolean): Boolean; + {$IFDEF INLINING} inline; {$ENDIF} +begin + if condition then + Result := not val else + Result := val; +end; +//------------------------------------------------------------------------------ + procedure TClipperOffset.DoGroupOffset(group: TGroup); var i,j, len, steps: Integer; @@ -437,8 +443,7 @@ procedure TClipperOffset.DoGroupOffset(group: TGroup); if group.endType = etPolygon then begin - if (group.lowestPathIdx < 0) then Exit; - //if (area == 0) return; // probably unhelpful (#430) + if (group.lowestPathIdx < 0) then fDelta := Abs(fDelta); if group.reversed then fGroupDelta := -fDelta else fGroupDelta := fDelta; @@ -457,7 +462,8 @@ procedure TClipperOffset.DoGroupOffset(group: TGroup); // calculate a sensible number of steps (for 360 deg for the given offset if (group.joinType = jtRound) or (group.endType = etRound) then begin - // arcTol - when fArcTolerance is undefined (0), the amount of + // calculate a sensible number of steps (for 360 deg for the given offset) + // arcTol - when arc_tolerance_ is undefined (0), the amount of // curve imprecision that's allowed is based on the size of the // offset (delta). Obviously very large offsets will almost always // require much less precision. See also offset_triginometry2.svg @@ -480,9 +486,9 @@ procedure TClipperOffset.DoGroupOffset(group: TGroup); fInPath := group.paths[i]; fNorms := nil; + len := Length(fInPath); //if a single vertex then build a circle or a square ... - len := Length(fInPath); if len = 1 then begin if fGroupDelta < 1 then Continue; @@ -509,13 +515,14 @@ procedure TClipperOffset.DoGroupOffset(group: TGroup); end; UpdateSolution; Continue; - end; - - // when shrinking, then make sure the path can shrink that far (#593) - // but also make sure this isn't a hole which will be expanding (#715) - if ((fGroupDelta < 0) <> group.isHoleList[i]) and - (Min(group.boundsList[i].Width, group.boundsList[i].Height) < - fGroupDelta *2) then Continue; + end; // end of offsetting a single point + + // when shrinking outer paths, make sure they can shrink this far (#593) + // also when shrinking holes, make sure they too can shrink this far (#715) + with group do + if ((fGroupDelta > 0) = ToggleBoolIf(isHoleList[i], reversed)) and + (Min(boundsList[i].Width, boundsList[i].Height) <= -fGroupDelta *2) then + Continue; if (len = 2) and (group.endType = etJoined) then begin From 98db5662e8dd1808a5a7b50c5605a2289bb390e8 Mon Sep 17 00:00:00 2001 From: angusj Date: Tue, 28 Nov 2023 22:48:04 +1000 Subject: [PATCH 94/95] Fixed a minor syntax error tripping some C++ compilers --- CPP/Clipper2Lib/src/clipper.offset.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CPP/Clipper2Lib/src/clipper.offset.cpp b/CPP/Clipper2Lib/src/clipper.offset.cpp index 514fbc8c..0282aa49 100644 --- a/CPP/Clipper2Lib/src/clipper.offset.cpp +++ b/CPP/Clipper2Lib/src/clipper.offset.cpp @@ -594,7 +594,7 @@ void ClipperOffset::DoGroupOffset(Group& group) // when shrinking outer paths, make sure they can shrink this far (#593) // also when shrinking holes, make sure they too can shrink this far (#715) - if (group_delta_ > 0 == ToggleBoolIf(*is_hole_it, group.is_reversed) && + if ((group_delta_ > 0) == ToggleBoolIf(*is_hole_it, group.is_reversed) && (std::min(path_rect_it->Width(), path_rect_it->Height()) <= -group_delta_ * 2) ) continue; From 2e68c81b0c99102f4177bc4fcfff8ad085296531 Mon Sep 17 00:00:00 2001 From: syunya_tolkunaga Date: Thu, 30 Nov 2023 10:54:13 +0900 Subject: [PATCH 95/95] Update namespace --- CSharp/Clipper2Lib/HashCode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CSharp/Clipper2Lib/HashCode.cs b/CSharp/Clipper2Lib/HashCode.cs index dd209738..d3e6c635 100644 --- a/CSharp/Clipper2Lib/HashCode.cs +++ b/CSharp/Clipper2Lib/HashCode.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; using System.Security.Cryptography; -namespace Clipper2Lib +namespace Drecom.Clipper2Lib { /*