diff --git a/CPP/BenchMark/PointInPolygonBenchmark.cpp b/CPP/BenchMark/PointInPolygonBenchmark.cpp index 401704a2..446a70f5 100644 --- a/CPP/BenchMark/PointInPolygonBenchmark.cpp +++ b/CPP/BenchMark/PointInPolygonBenchmark.cpp @@ -1,13 +1,46 @@ #include "benchmark/benchmark.h" #include "clipper2/clipper.h" #include "CommonUtils.h" +#include "ClipFileLoad.h" #include #include +#include using namespace Clipper2Lib; -using benchmark::State; +enum ConsoleTextColor { + reset = 0, + //normal text colors ... + red = 31, green = 32, yellow = 33, blue = 34, magenta = 35, cyan = 36, white = 37, + //bold text colors ... + red_bold = 91, green_bold = 92, yellow_bold = 93, blue_bold = 94, + magenta_bold = 95, cyan_bold = 96, white_bold = 97 +}; + +///////////////////////////////////////////////////////////////////////////// +// SetConsoleTextColor: a simple class to adjust Windows' Console Text Colors +///////////////////////////////////////////////////////////////////////////// + +struct SetConsoleTextColor +{ +private: + ConsoleTextColor _color; +public: + SetConsoleTextColor(ConsoleTextColor color) : _color(color) {}; + + static friend std::ostream& operator<< (std::ostream& out, SetConsoleTextColor const& scc) + { + return out << "\x1B[" << scc._color << "m"; + } +}; +///////////////////////////////////////////////////////////////////////////// + + +typedef std::function PipFunction; + +///////////////////////////////////////////////////////// // PIP1: This is the current Clipper2 PointInPolygon code +///////////////////////////////////////////////////////// template inline PointInPolygonResult PIP1(const Point& pt, const Path& polygon) { @@ -90,9 +123,11 @@ inline PointInPolygonResult PIP1(const Point& pt, const Path& polygon) } -// PIP2: This is a not fully tested modification of the current -// Clipper2 PointInPolygon code. It's a little simpler and it's -// also marginally faster. +///////////////////////////////////////////////////////// +// PIP2: This is a not fully tested modification of the +// current Clipper2 PointInPolygon code. It's a little +// simpler and also marginally faster. +///////////////////////////////////////////////////////// template inline PointInPolygonResult PIP2(const Point& pt, const Path& polygon) { @@ -161,14 +196,17 @@ inline PointInPolygonResult PIP2(const Point& pt, const Path& polygon) PointInPolygonResult::IsOutside; } +///////////////////////////////////////////////////////// // PIP3: An entirely different algorithm for comparision. // "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) { + if (!path.size()) return PointInPolygonResult::IsOutside; T x1, y1, x2, y2; int k = 0; Path::const_iterator itPrev = path.cend() - 1; @@ -192,7 +230,7 @@ static PointInPolygonResult PIP3(const Point &pt, const Path &path) if (f > 0) ++k; else if (f == 0) return PointInPolygonResult::IsOn; } - else if ((y1 > 0) && (y2 <= 0)) + else if ((y1 > 0) && (y2 <= 0)) { int64_t f = x1 * y2 - x2 * y1; if (f < 0) ++k; @@ -213,81 +251,128 @@ static PointInPolygonResult PIP3(const Point &pt, const Path &path) } -// globals -Paths64 paths; +///////////////////////////////////////////////////////// +// global data structures +///////////////////////////////////////////////////////// + Point64 mp; -std::vector pipResults; -PointInPolygonResult pip1 = PointInPolygonResult::IsOn; -PointInPolygonResult pip2 = PointInPolygonResult::IsOn; -PointInPolygonResult pip3 = PointInPolygonResult::IsOn; +Paths64 paths; +Path64 points_of_interest_outside; +Path64 points_of_interest_inside; +std::vector < std::vector > pipResults; +///////////////////////////////////////////////////////// +// Benchmark callback functions +///////////////////////////////////////////////////////// + static void BM_PIP1(benchmark::State& state) { - for (auto _ : state) - { - pip1 = PIP1(mp, paths[state.range(0)]); - } + int64_t idx = state.range(0); + for (auto _ : state) + pipResults[0][idx] = PIP1(mp, paths[idx]); + } static void BM_PIP2(benchmark::State& state) { + int64_t idx = state.range(0); for (auto _ : state) - { - pip2 = PIP2(mp, paths[state.range(0)]); - } + pipResults[1][idx] = PIP2(mp, paths[idx]); } static void BM_PIP3(benchmark::State& state) { + int64_t idx = state.range(0); for (auto _ : state) - { - pip3 = PIP3(mp, paths[state.range(0)]); - } + pipResults[2][idx] = PIP3(mp, paths[idx]); } +///////////////////////////////////////////////////////// +// Miscellaneous functions +///////////////////////////////////////////////////////// + static void CustomArguments(benchmark::internal::Benchmark* b) { for (int i = 0; i < paths.size(); ++i) b->Args({ i }); } -enum ConsoleTextColor { - reset = 0, - //normal text colors ... - red = 31, green = 32, yellow = 33, blue = 34, magenta = 35, cyan = 36, white = 37, - //bold text colors ... - red_bold = 91, green_bold = 92, yellow_bold = 93, blue_bold = 94, - magenta_bold = 95, cyan_bold = 96, white_bold = 97 -}; +inline PipFunction GetPIPFunc(int index) +{ + PointInPolygonResult(*result)(const Point64&, const Path64&); + switch (index) + { + case 0: result = PIP1; break; + case 1: result = PIP2; break; + case 2: result = PIP3; break; + default: throw "oops! - wrong function!"; + } + return result; +} -struct SetConsoleTextColor +///////////////////////////////////////////////////////// +// Test functions +///////////////////////////////////////////////////////// + +// DoErrorTest1 +///////////////////////////////////////////////////////// + +static void DoErrorTest1_internal(const Path64& pts_of_int, const Paths64& paths, + PipFunction pip_func, PointInPolygonResult expected) { -private: - ConsoleTextColor _color; -public: - SetConsoleTextColor(ConsoleTextColor color) : _color(color) {}; + Path64 error_points; - static friend std::ostream& operator<< (std::ostream& out, SetConsoleTextColor const& scc) + for (Point64 poi : pts_of_int) { - return out << "\x1B[" << scc._color << "m"; + size_t inside_cnt = 0; + for (const Path64& path : paths) + if (pip_func(poi, path) == PointInPolygonResult::IsInside) ++inside_cnt; + switch (expected) + { + case PointInPolygonResult::IsInside: + if (inside_cnt != 1) error_points.push_back(poi); break; + case PointInPolygonResult::IsOutside: + if (inside_cnt) error_points.push_back(poi); break; + } } -}; -static void DoErrorTest(int index) -{ - PointInPolygonResult(*pip_func)(const Point64&, const Path64&); - switch (index) + if (error_points.size()) { - case 1: pip_func = PIP1; break; - case 2: pip_func = PIP2; break; - case 3: pip_func = PIP2; break; - default: throw "oops! - wrong function!"; + size_t high_error = error_points.size() - 1; + std::cout << SetConsoleTextColor(red_bold) << " Errors at "; + for (size_t i = 0; i < high_error; ++i) std::cout << "(" << error_points[i] << "), "; + std::cout << "(" << error_points[high_error] << ")." << SetConsoleTextColor(reset) << std::endl; } + else + std::cout << " No errors found." << std::endl; +} + +static void DoErrorTest1(int index) +{ + PipFunction pip_func = GetPIPFunc(index); + + std::cout << SetConsoleTextColor(green_bold) << + "Testing PIP" << index +1 << "/outside:" << SetConsoleTextColor(reset); + DoErrorTest1_internal(points_of_interest_outside, paths, + pip_func, PointInPolygonResult::IsOutside); + + std::cout << SetConsoleTextColor(green_bold) << + "Testing PIP" << index +1 << "/inside :" << SetConsoleTextColor(reset); + DoErrorTest1_internal(points_of_interest_inside, paths, + pip_func, PointInPolygonResult::IsInside); +} + +// DoErrorTest2 +///////////////////////////////////////////////////////// +static void DoErrorTest2(int index) +{ + PipFunction pip_func = GetPIPFunc(index); + std::vector errors; std::cout << SetConsoleTextColor(green_bold) << - "Testing PIP" << index << SetConsoleTextColor(reset) <<":"; + "Testing PIP" << index +1 << SetConsoleTextColor(reset) <<":"; for (size_t i = 0; i < paths.size(); ++i) - if (pip_func(mp, paths[i]) != pipResults[i]) errors.push_back(i); + if (pip_func(mp, paths[i]) != pipResults[0][i]) errors.push_back(i); if (errors.size()) { size_t high_error = errors.size() - 1; @@ -301,98 +386,173 @@ static void DoErrorTest(int index) std::cout << " No errors found." << std::endl; } -int main(int argc, char** argv) { - - enum DoTests { do_error_test_only, do_benchmark_only, do_all_tests }; - const DoTests do_tests = do_all_tests;// do_error_test_only;// - - if (do_tests != do_benchmark_only) - { - // stress test PIP2 with unusual polygons - mp = Point64(10, 10); - - 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 << "Error Tests:" << std::endl << std::endl; - - DoErrorTest(1); - DoErrorTest(2); - DoErrorTest(3); - std::cout << std::endl; - - if (do_tests != do_all_tests) - { - std::string _; - std::getline(std::cin, _); - return 0; - } - } - - if (do_tests == do_error_test_only) return 0; - - std::cout << "Benchmarks 1: " << - "benchmarking PIP on a single elliptical path" << std::endl << std::endl; +///////////////////////////////////////////////////////// +// Benchmark functions +///////////////////////////////////////////////////////// +// DoBenchmark1 +///////////////////////////////////////////////////////// +static void DoBenchmark1() +{ // compare 3 PIP algorithms - const int width = 600000, height = 400000; - mp = Point64(width / 2, height / 2); - paths.clear(); - paths.push_back(Ellipse(mp, 10000, 6000, 10000)); - std::vector args{ 0 }; - benchmark::Initialize(&argc, argv); - BENCHMARK(BM_PIP1)->Args(args); // current Clipper2 - BENCHMARK(BM_PIP2)->Args(args); // modified Clipper2 - BENCHMARK(BM_PIP3)->Args(args); // Hao et al. (2018) + pipResults.clear(); + pipResults.resize(3); + for (size_t i = 0; i < 3; ++i) pipResults[i].resize(paths.size()); + benchmark::Initialize(0, nullptr); + 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(benchmark::CreateDefaultDisplayReporter()); benchmark::ClearRegisteredBenchmarks(); benchmark::Shutdown(); +} - std::cout << std::endl << std::endl << - "Setting up before Benchmarks 2 ..." << - std::endl << std::endl; - - paths.clear(); - srand((unsigned)time(0)); - for (int i = 0, count = 10000; i < 5; ++i, count *= 10) - paths.push_back(MakeRandomPoly(width, height, count)); - - std::cout << "Benchmarks 2: " << - "benchmarking PIP using a single self-intersecting polygon" << std::endl << std::endl; +// DoBenchmark2 +///////////////////////////////////////////////////////// +static bool DoBenchmark2() +{ + pipResults.clear(); + pipResults.resize(3); + for (size_t i = 0; i < 3; ++i) pipResults[i].resize(paths.size()); - benchmark::Initialize(&argc, argv); + benchmark::Initialize(0, nullptr); 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(benchmark::CreateDefaultDisplayReporter()); - if (pip2 != pip1 || pip3 != pip1) + std::cout << std::endl; + // compare results to ensure they all agree :) + bool result = true; + const std::string bad_filename = "test_pip_"; + for (size_t i = 0; i < pipResults[0].size(); ++i) { - 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; + if ((pipResults[0][i] == pipResults[1][i]) && + (pipResults[0][i] == pipResults[2][i])) continue; + + if (pipResults[0][i] != pipResults[1][i]) + std::cout << "PIP2 returned the " << SetConsoleTextColor(red_bold) << "wrong " << + SetConsoleTextColor(reset) << "result:" << std::endl; + if (pipResults[0][i] != pipResults[2][i]) + std::cout << "PIP3 returned the " << SetConsoleTextColor(red_bold) << "wrong " << + SetConsoleTextColor(reset) << "result:" << std::endl; + + std::cout << "Problematic PIP path saved to - " << bad_filename << i << ".txt" << std::endl; + std::ofstream of(bad_filename); + of << paths[i] << std::endl; + of.close(); + result = false; } + return true; +} + +///////////////////////////////////////////////////////// +// Main Entry +///////////////////////////////////////////////////////// + +int main(int argc, char** argv) +{ + std::cout << SetConsoleTextColor(cyan_bold) << + "Simple error checks ..." << SetConsoleTextColor(reset) << + std::endl; + + ////////////////////////////////////////////////////////////// + std::cout << std::endl << SetConsoleTextColor(yellow_bold) << + "Tests for errors #1:" << SetConsoleTextColor(reset) << + std::endl << std::endl; + ////////////////////////////////////////////////////////////// + + // #1. path is constant but points of interest change + + Paths64 subject, subject_open, clip; + ClipType ct = ClipType::None; + FillRule fr = FillRule::EvenOdd; + int64_t area = 0, count = 0; + const std::string test_file = "../../../../../Tests/PolytreeHoleOwner2.txt"; + points_of_interest_outside = + MakePath({ 21887,10420, 21726,10825, 21662,10845, 21617,10890 }); + points_of_interest_inside = + MakePath({ 21887,10430, 21843,10520, 21810,10686, 21900,10461 }); + if (!FileExists(test_file)) return 1; + std::ifstream ifs(test_file); + if (!ifs || !ifs.good()) return 1; + LoadTestNum(ifs, 1, paths, subject_open, clip, area, count, ct, fr); + ifs.close(); + + for (int i = 0; i < 3; ++i) DoErrorTest1(i); + + ////////////////////////////////////////////////////////////// + std::cout << std::endl << SetConsoleTextColor(yellow_bold) << + "Tests for errors #2:" << SetConsoleTextColor(reset) << + std::endl << std::endl; + ////////////////////////////////////////////////////////////// + + // #2. point of interest is now constant (10,10) but path changes + + mp = Point64(10, 10); + paths.clear(); + pipResults.clear(); + pipResults.resize(1); + paths.push_back({}); // ie test an empty path + pipResults[0].push_back(PointInPolygonResult::IsOutside); + paths.push_back(MakePath({ 100,10, 200,10 })); + pipResults[0].push_back(PointInPolygonResult::IsOutside); + paths.push_back(MakePath({ 100,10, 200,10, 10,10, 20,20 })); + pipResults[0].push_back(PointInPolygonResult::IsOn); + paths.push_back(MakePath({ 10,10 })); + pipResults[0].push_back(PointInPolygonResult::IsOn); + paths.push_back(MakePath({ 100,10 })); + pipResults[0].push_back(PointInPolygonResult::IsOutside); + paths.push_back(MakePath({ 100,10, 110,20, 200,10, 10,10, 20,20 })); + pipResults[0].push_back(PointInPolygonResult::IsOn); + paths.push_back(MakePath({ 100,10, 110,20, 200,10, 20,20 })); + pipResults[0].push_back(PointInPolygonResult::IsOutside); + paths.push_back(MakePath({ 200,0, 0,0, 10,20, 200,0, 20,0 })); + pipResults[0].push_back(PointInPolygonResult::IsInside); + paths.push_back(MakePath({ 0,0, 20,20, 100,0 })); + pipResults[0].push_back(PointInPolygonResult::IsOn); + + for (int i = 0; i < 3; ++i) DoErrorTest2(i); + std::cout << std::endl; + + + ////////////////////////////////////////////////////////////// + std::cout << std::endl << SetConsoleTextColor(cyan_bold) << + "Benchmarking ..." << SetConsoleTextColor(reset) << std::endl; + ////////////////////////////////////////////////////////////// + + int width = 600000, height = 400000; + const int power10_lo = 4, power10_high = 8; + mp = Point64(width / 2, height / 2); + + std::cout << std::endl << SetConsoleTextColor(yellow_bold) << + "Benchmarks 1:" << SetConsoleTextColor(reset) << std::endl; + ////////////////////////////////////////////////////////////// + paths.clear(); + for (int i = power10_lo; i <= power10_high; ++i) + paths.push_back(Ellipse(mp, width / 2.0, height / 2.0, (unsigned)std::pow(10, i))); + std::cout << "A single elliptical path " << std::endl << + "Edge counts between 10^" << power10_lo << " and 10^" << + power10_high << std::endl << std::endl; + + DoBenchmark1(); + std::cout << std::endl; + + + std::cout << std::endl << SetConsoleTextColor(yellow_bold) << + "Benchmarks 2:" << SetConsoleTextColor(reset) << std::endl; + ////////////////////////////////////////////////////////////// + std::cout << "A random self-intersecting polygon (" << + width << " x " << height << ")" << std::endl << + "Edge counts between 10^" << power10_lo << " and 10^" << + power10_high << ". " << std::endl << + "Point (" << mp << ")" << std::endl << std::endl; + + paths.clear(); + for (int i = power10_lo; i <= power10_high; ++i) + paths.push_back(MakeRandomPoly(width, height, (unsigned)std::pow(10, i))); + DoBenchmark2(); + std::cout << std::endl; return 0; } diff --git a/CPP/BenchMark/StripDuplicateBenchmark.cpp b/CPP/BenchMark/StripDuplicateBenchmark.cpp index 92c07196..36d7b45a 100644 --- a/CPP/BenchMark/StripDuplicateBenchmark.cpp +++ b/CPP/BenchMark/StripDuplicateBenchmark.cpp @@ -55,9 +55,9 @@ static void BM_StripDuplicatesCopyVersion(benchmark::State &state) { for (auto _ : state) { state.PauseTiming(); - int width = state.range(0); - int height = state.range(1); - int count = state.range(2); + int width = (int)state.range(0); + int height = (int)state.range(1); + int count = (int)state.range(2); op1.push_back(MakeRandomPoly(width, height, count)); state.ResumeTiming(); StripDuplicatesCopyVersion(op1, true); @@ -69,9 +69,9 @@ static void BM_StripDuplicates(benchmark::State &state) { Paths64 op1; for (auto _ : state) { state.PauseTiming(); - int width = state.range(0); - int height = state.range(1); - int count = state.range(2); + int width = (int)state.range(0); + int height = (int)state.range(1); + int count = (int)state.range(2); op1.push_back(MakeRandomPoly(width, height, count)); state.ResumeTiming(); StripDuplicates(op1, true); diff --git a/CPP/Clipper2Lib/src/clipper.engine.cpp b/CPP/Clipper2Lib/src/clipper.engine.cpp index 9358b74b..027a5676 100644 --- a/CPP/Clipper2Lib/src/clipper.engine.cpp +++ b/CPP/Clipper2Lib/src/clipper.engine.cpp @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 22 November 2023 * +* Date : 1 December 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -63,6 +63,7 @@ namespace Clipper2Lib { } }; + inline bool IsOdd(int val) { return (val & 1) ? true : false; @@ -2536,8 +2537,8 @@ namespace Clipper2Lib { if (IsHotEdge(horz) && IsJoined(*e)) Split(*e, e->top); - //if (IsHotEdge(horz) != IsHotEdge(*e)) - // DoError(undefined_error_i); + //if (IsHotEdge(horz) != IsHotEdge(*e)) + // DoError(undefined_error_i); if (IsHotEdge(horz)) { @@ -2758,8 +2759,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)) return; + if (!prev || + !IsHotEdge(e) || !IsHotEdge(*prev) || + IsHorizontal(e) || IsHorizontal(*prev) || + IsOpen(e) || IsOpen(*prev) ) return; if ((pt.y < e.top.y + 2 || pt.y < prev->top.y + 2) && ((e.bot.y > pt.y) || (prev->bot.y > pt.y))) return; // avoid trivial joins @@ -2784,8 +2787,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)) return; + if (!next || + !IsHotEdge(e) || !IsHotEdge(*next) || + IsHorizontal(e) || IsHorizontal(*next) || + IsOpen(e) || IsOpen(*next)) return; if ((pt.y < e.top.y +2 || pt.y < next->top.y +2) && ((e.bot.y > pt.y) || (next->bot.y > pt.y))) return; // avoid trivial joins diff --git a/CPP/Utils/CommonUtils.h b/CPP/Utils/CommonUtils.h index c2e2d05c..68cee2ee 100644 --- a/CPP/Utils/CommonUtils.h +++ b/CPP/Utils/CommonUtils.h @@ -1,25 +1,36 @@ #include +#include #include "clipper2/clipper.h" #ifndef __COMMONUTILS_H__ #define __COMMONUTILS_H__ Clipper2Lib::Path64 MakeRandomPoly(int width, int height, unsigned vertCnt) { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> w(0, width); + std::uniform_int_distribution<> h(0, height); + 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)); + result.push_back(Point64(w(gen), h(gen))); return result; } Clipper2Lib::PathD MakeRandomPolyD(int width, int height, unsigned vertCnt) { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> w(0, width); + std::uniform_int_distribution<> h(0, height); + 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)); + result.push_back(PointD(w(gen), h(gen))); return result; } diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index eaee6a40..bcc8ca57 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -1,6 +1,6 @@ /******************************************************************************* * Author : Angus Johnson * -* Date : 22 November 2023 * +* Date : 1 December 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2393,8 +2393,10 @@ private void CheckJoinLeft(Active e, Point64 pt, bool checkCurrX = false) { Active? prev = e.prevInAEL; - if (prev == null || IsOpen(e) || IsOpen(prev) || - !IsHotEdge(e) || !IsHotEdge(prev)) return; + if (prev == null || + !IsHotEdge(e) || !IsHotEdge(prev) || + IsHorizontal(e) || IsHorizontal(prev) || + IsOpen(e) || IsOpen(prev)) return; if ((pt.Y < e.top.Y + 2 || pt.Y < prev.top.Y + 2) && // avoid trivial joins ((e.bot.Y > pt.Y) || (prev.bot.Y > pt.Y))) return; // (#490) @@ -2420,8 +2422,10 @@ private void CheckJoinRight(Active e, Point64 pt, bool checkCurrX = false) { Active? next = e.nextInAEL; - if (IsOpen(e) || !IsHotEdge(e) || IsJoined(e) || - next == null || IsOpen(next) || !IsHotEdge(next)) return; + if (next == null || + !IsHotEdge(e) || !IsHotEdge(next) || + IsHorizontal(e) || IsHorizontal(next) || + IsOpen(e) || IsOpen(next)) return; if ((pt.Y < e.top.Y + 2 || pt.Y < next.top.Y + 2) && // avoid trivial joins ((e.bot.Y > pt.Y) || (next.bot.Y > pt.Y))) return; // (#490) diff --git a/Delphi/Clipper2Lib/Clipper.Engine.pas b/Delphi/Clipper2Lib/Clipper.Engine.pas index da38f00a..9339134f 100644 --- a/Delphi/Clipper2Lib/Clipper.Engine.pas +++ b/Delphi/Clipper2Lib/Clipper.Engine.pas @@ -2,7 +2,7 @@ (******************************************************************************* * Author : Angus Johnson * -* Date : 22 November 2023 * +* Date : 1 December 2023 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2023 * * Purpose : This is the main polygon clipping module * @@ -2374,8 +2374,10 @@ procedure TClipperBase.CheckJoinLeft(e: PActive; prev: PActive; begin prev := e.prevInAEL; - if IsOpen(e) or not IsHotEdge(e) or not Assigned(prev) or - IsOpen(prev) or not IsHotEdge(prev) then Exit; + if not Assigned(prev) or + not IsHotEdge(e) or not IsHotEdge(prev) or + IsHorizontal(e) or IsHorizontal(prev) or + IsOpen(e) or IsOpen(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; // (#490) @@ -2403,8 +2405,10 @@ procedure TClipperBase.CheckJoinRight(e: PActive; next: PActive; begin next := e.nextInAEL; - if IsOpen(e) or not IsHotEdge(e) or not Assigned(next) or - not IsHotEdge(next) or IsOpen(next) then Exit; + if not Assigned(next) or + not IsHotEdge(e) or not IsHotEdge(next) or + IsHorizontal(e) or IsHorizontal(next) or + IsOpen(e) 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; // (#490)