From 234f062ad424081df0e9889135aab185b2bc6438 Mon Sep 17 00:00:00 2001
From: tamasmeszaros <meszaros.q@gmail.com>
Date: Thu, 30 Sep 2021 15:49:12 +0200
Subject: [PATCH 1/3] Fast convex polygon intersection test with rotating
 calipers

---
 src/libslic3r/Geometry.cpp        | 211 +++++++++++++++++++++++++++++-
 src/libslic3r/Geometry.hpp        |   2 +
 tests/libslic3r/CMakeLists.txt    |   1 +
 tests/libslic3r/test_geometry.cpp | 173 ++++++++++++++++++++++++
 4 files changed, 386 insertions(+), 1 deletion(-)

diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp
index 321443204..bbb1f0d6b 100644
--- a/src/libslic3r/Geometry.cpp
+++ b/src/libslic3r/Geometry.cpp
@@ -20,6 +20,26 @@
 #include <boost/algorithm/string/split.hpp>
 #include <boost/log/trivial.hpp>
 
+#if defined(_MSC_VER) && defined(__clang__)
+#define BOOST_NO_CXX17_HDR_STRING_VIEW
+#endif
+
+#include <libslic3r/Int128.hpp>
+
+#include <boost/multiprecision/integer.hpp>
+
+namespace Slic3r {
+
+#if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__)
+using int128_t = boost::multiprecision::int128_t;
+#else
+using int128_t = __int128;
+#endif
+
+using int256_t = boost::multiprecision::int256_t;
+
+} // namespace Slic3r
+
 #ifdef SLIC3R_DEBUG
 #include "SVG.hpp"
 #endif
@@ -1543,4 +1563,193 @@ double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to)
     return (axis.z() < 0) ? -angle : angle;
 }
 
-} }
+namespace rotcalip {
+
+inline int128_t magnsq(const Point &p)
+{
+    return int128_t(p.x()) * p.x() + int64_t(p.y()) * p.y();
+}
+
+inline int128_t dot(const Point &a, const Point &b)
+{
+    return int128_t(a.x()) * b.x() + int64_t(a.y()) * b.y();
+}
+
+// Compares the angle enclosed by vectors dir and dirA (alpha) with the angle
+// enclosed by -dir and dirB (beta). Returns -1 if alpha is less than beta, 0
+// if they are equal and 1 if alpha is greater than beta. Note that dir is
+// reversed for beta, because it represents the opposite side of a caliper.
+int cmp_angles(const Point &dir, const Point &dirA, const Point &dirB) {
+    int128_t dotA = dot(dir, dirA);
+    int128_t dotB = dot(-dir, dirB);
+    int256_t dcosa = int256_t(magnsq(dirB)) * int256_t(std::abs(dotA)) * dotA;
+    int256_t dcosb = int256_t(magnsq(dirA)) * int256_t(std::abs(dotB)) * dotB;
+    int256_t diff = dcosa - dcosb;
+
+    return diff > 0? -1 : (diff < 0 ? 1 : 0);
+}
+
+// A helper class to navigate on a polygon. Given a vertex index, one can
+// get the edge belonging to that vertex, the coordinates of the vertex, the
+// next and previous edges. Stuff that is needed in the rotating calipers algo.
+class Idx
+{
+    size_t m_idx;
+    const Polygon *m_poly;
+public:
+    explicit Idx(const Polygon &p): m_idx{0}, m_poly{&p} {}
+    explicit Idx(size_t idx, const Polygon &p): m_idx{idx}, m_poly{&p} {}
+
+    size_t idx() const { return m_idx; }
+    void set_idx(size_t i) { m_idx = i; }
+    size_t next() const { return (m_idx + 1) % m_poly->size(); }
+    size_t inc() { return m_idx = (m_idx + 1) % m_poly->size(); }
+    Point prev_dir() const {
+        return pt() - (*m_poly)[(m_idx + m_poly->size() - 1) % m_poly->size()];
+    }
+
+    const Point &pt() const { return (*m_poly)[m_idx]; }
+    const Point dir() const { return (*m_poly)[next()] - pt(); }
+    const Point  next_dir() const
+    {
+        return (*m_poly)[(m_idx + 2) % m_poly->size()] - (*m_poly)[next()];
+    }
+    const Polygon &poly() const { return *m_poly; }
+};
+
+enum class AntipodalVisitMode { Full, SkipParallelSegments };
+
+// Visit all antipodal pairs starting from the initial ia, ib pair which
+// has to be a valid antipodal pair (not checked). fn is called for every
+// antipodal pair encountered including the initial one.
+// The callback Fn has a signiture of bool(size_t i, size_t j, const Point &dir)
+// where i,j are the vertex indices of the antipodal pair and dir is the
+// direction of the calipers touching the i vertex.
+template<AntipodalVisitMode mode = AntipodalVisitMode::Full, class Fn>
+void visit_antipodals (Idx& ia, Idx &ib, Fn &&fn)
+{
+    // Set current caliper direction to be the lower edge angle from X axis
+    int cmp = cmp_angles(ia.prev_dir(), ia.dir(), ib.dir());
+    Idx *current = cmp <= 0 ? &ia : &ib, *other = cmp <= 0 ? &ib : &ia;
+    bool visitor_continue = true;
+
+    size_t a_start = ia.idx(), b_start = ib.idx();
+    bool a_finished = false, b_finished = false;
+
+    while (visitor_continue && !(a_finished && b_finished)) {
+        Point current_dir_a = current == &ia ? current->dir() : -current->dir();
+        visitor_continue = fn(ia.idx(), ib.idx(), current_dir_a);
+
+        if constexpr (mode == AntipodalVisitMode::Full)
+            if (cmp == 0 && visitor_continue) {
+                visitor_continue = fn(current == &ia ? ia.idx() : ia.next(),
+                                      current == &ib ? ib.idx() : ib.next(),
+                                      current_dir_a);
+            }
+
+        cmp = cmp_angles(current->dir(), current->next_dir(), other->dir());
+
+        current->inc();
+        if (cmp > 0) {
+            std::swap(current, other);
+        }
+
+        if (ia.idx() == a_start) a_finished = true;
+        if (ib.idx() == b_start) b_finished = true;
+    }
+}
+
+static bool is_left(const Point &a, const Point &b, const Point &c)
+{
+    Vec<2, int64_t> V = (b - a).cast<int64_t>();
+    Vec<2, int64_t> W = (c - a).cast<int64_t>();
+
+    return V.x() * W.y() - V.y() * W.x() > 0;
+}
+
+} // namespace rotcalip
+
+bool intersects(const Polygon &A, const Polygon &B)
+{
+    using namespace rotcalip;
+
+    // Establish starting antipodals as extremes in XY plane. Use the
+    // easily obtainable bounding boxes to check if A and B is disjoint
+    // and return false if the are.
+
+    struct BB
+    {
+        size_t         xmin = 0, xmax = 0, ymin = 0, ymax = 0;
+        const Polygon &P;
+        static bool cmpy(const Point &l, const Point &u)
+        {
+            return l.y() < u.y() || (l.y() == u.y() && l.x() < u.x());
+        }
+
+        BB(const Polygon &poly): P{poly}
+        {
+            for (size_t i = 0; i < P.size(); ++i) {
+                if (P[i] < P[xmin]) xmin = i;
+                if (P[xmax] < P[i]) xmax = i;
+                if (cmpy(P[i], P[ymin])) ymin = i;
+                if (cmpy(P[ymax], P[i])) ymax = i;
+            }
+        }
+    };
+
+    BB bA{A}, bB{B};
+    BoundingBox bbA{{A[bA.xmin].x(), A[bA.ymin].y()}, {A[bA.xmax].x(), A[bA.ymax].y()}};
+    BoundingBox bbB{{B[bB.xmin].x(), B[bB.ymin].y()}, {B[bB.xmax].x(), B[bB.ymax].y()}};
+
+    if (!bbA.overlap(bbB))
+        return false;
+
+    // Establish starting antipodals as extreme vertex pairs in X or Y direction
+    // which reside on different polygons. If no such pair is found, the two
+    // polygons are certainly not disjoint.
+    Idx imin{bA.xmin, A}, imax{bB.xmax, B};
+    if (B[bB.xmin] < imin.pt())  imin = Idx{bB.xmin, B};
+    if (imax.pt()  < A[bA.xmax]) imax = Idx{bA.xmax, A};
+    if (&imin.poly() == &imax.poly()) {
+        imin = Idx{bA.ymin, A};
+        imax = Idx{bB.ymax, B};
+        if (B[bB.ymin] < imin.pt())  imin = Idx{bB.ymin, B};
+        if (imax.pt()  < A[bA.ymax]) imax = Idx{bA.ymax, A};
+    }
+
+    if (&imin.poly() == &imax.poly())
+        return true;
+
+    bool found_divisor;
+    visit_antipodals<AntipodalVisitMode::SkipParallelSegments>(
+        imin, imax,
+        [&imin, &imax, &found_divisor](size_t ia, size_t ib, const Point &dir) {
+            //        std::cout << "A" << ia << " B" << ib << " dir " <<
+            //        dir.x() << " " << dir.y() << std::endl;
+            const Polygon &A = imin.poly(), &B = imax.poly();
+
+            Point ref_a     = A[(ia + 2) % A.size()],
+                  ref_b     = B[(ib + 2) % B.size()];
+            Point Anext     = A[ia] + dir;
+            bool  is_left_a = is_left(A[ia], Anext, ref_a);
+            bool  is_left_b = is_left(B[ib], B[ib] - dir, ref_b);
+
+            // If both reference points are on the left (or right) of the
+            // support line and the opposite support line is to the righ (or
+            // left), the divisor line is found. We only test the reference
+            // point, as by definition, if that is on one side, all the other
+            // points must be on the same side of a support line.
+            if (is_left(A[ia], Anext, B[ib])) {
+                found_divisor = !is_left_a && !is_left_b;
+            } else {
+                found_divisor = is_left_a && is_left_b;
+            }
+
+            return !found_divisor;
+        });
+
+    // Intersects if the divisor was not found
+    return !found_divisor;
+}
+
+}} // namespace Slic3r::Geometry
diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp
index c6af515c8..bb583c33c 100644
--- a/src/libslic3r/Geometry.hpp
+++ b/src/libslic3r/Geometry.hpp
@@ -532,6 +532,8 @@ inline bool is_rotation_ninety_degrees(const Vec3d &rotation)
     return is_rotation_ninety_degrees(rotation.x()) && is_rotation_ninety_degrees(rotation.y()) && is_rotation_ninety_degrees(rotation.z());
 }
 
+bool intersects(const Polygon &convex_poly1, const Polygon &convex_poly2);
+
 } } // namespace Slicer::Geometry
 
 #endif
diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt
index 575878cf2..05898db28 100644
--- a/tests/libslic3r/CMakeLists.txt
+++ b/tests/libslic3r/CMakeLists.txt
@@ -23,6 +23,7 @@ add_executable(${_TEST_NAME}_tests
     test_png_io.cpp
     test_timeutils.cpp
     test_indexed_triangle_set.cpp
+    ../libnest2d/printer_parts.cpp
 	)
 
 if (TARGET OpenVDB::openvdb)
diff --git a/tests/libslic3r/test_geometry.cpp b/tests/libslic3r/test_geometry.cpp
index 24e0908cc..7373eaa6a 100644
--- a/tests/libslic3r/test_geometry.cpp
+++ b/tests/libslic3r/test_geometry.cpp
@@ -9,6 +9,14 @@
 #include "libslic3r/ClipperUtils.hpp"
 #include "libslic3r/ShortestPath.hpp"
 
+#include <random>
+#include "libnest2d/tools/benchmark.h"
+#include "libslic3r/SVG.hpp"
+
+#include "../libnest2d/printer_parts.hpp"
+
+#include <unordered_set>
+
 using namespace Slic3r;
 
 TEST_CASE("Polygon::contains works properly", "[Geometry]"){
@@ -452,3 +460,168 @@ SCENARIO("Ported from xs/t/14_geometry.t", "[Geometry]"){
     	REQUIRE(! Slic3r::Geometry::directions_parallel(M_PI /2, PI, M_PI /180));
     }
 }
+
+static Polygon gen_convex_poly(std::mt19937_64 &rg, size_t point_cnt)
+{
+    std::uniform_int_distribution<coord_t> dist(0, 100);
+
+    Polygon out;
+    out.points.reserve(point_cnt);
+
+    coord_t tr = dist(rg) * 2 / SCALING_FACTOR;
+
+    for (size_t i = 0; i < point_cnt; ++i)
+        out.points.emplace_back(tr + dist(rg) / SCALING_FACTOR,
+                                tr + dist(rg) / SCALING_FACTOR);
+
+    return Geometry::convex_hull(out.points);
+}
+
+TEST_CASE("Convex polygon intersection on two disjoint squares", "[Geometry][Rotcalip]") {
+    Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
+    A.scale(1. / SCALING_FACTOR);
+
+    Polygon B = A;
+    B.translate(20 / SCALING_FACTOR, 0);
+
+    bool is_inters = Geometry::intersects(A, B);
+
+    REQUIRE(is_inters != true);
+}
+
+TEST_CASE("Convex polygon intersection on two intersecting squares", "[Geometry][Rotcalip]") {
+    Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
+    A.scale(1. / SCALING_FACTOR);
+
+    Polygon B = A;
+    B.translate(5 / SCALING_FACTOR, 5 / SCALING_FACTOR);
+
+    bool is_inters = Geometry::intersects(A, B);
+
+    REQUIRE(is_inters == true);
+}
+
+TEST_CASE("Convex polygon intersection test on random polygons", "[Geometry]") {
+    constexpr size_t TEST_CNT = 1000;
+    constexpr size_t POINT_CNT = 1000;
+
+    std::mt19937_64 rg{std::random_device{}()};
+    Benchmark bench;
+
+    auto tests = reserve_vector<std::pair<Polygon, Polygon>>(TEST_CNT);
+    auto results = reserve_vector<bool>(TEST_CNT);
+    auto expects = reserve_vector<bool>(TEST_CNT);
+
+    for (size_t i = 0; i < TEST_CNT; ++i) {
+        tests.emplace_back(gen_convex_poly(rg, POINT_CNT), gen_convex_poly(rg, POINT_CNT));
+    }
+
+    bench.start();
+    for (const auto &test : tests)
+        results.emplace_back(Geometry::intersects(test.first, test.second));
+    bench.stop();
+
+    std::cout << "Test time: " << bench.getElapsedSec() << std::endl;
+
+    bench.start();
+    for (const auto &test : tests)
+        expects.emplace_back(!intersection(test.first, test.second).empty());
+    bench.stop();
+
+    std::cout << "Clipper time: " << bench.getElapsedSec() << std::endl;
+
+    REQUIRE(results.size() == expects.size());
+
+    for (size_t i = 0; i < results.size(); ++i) {
+        // std::cout << expects[i] << " ";
+
+        if (results[i] != expects[i]) {
+            SVG svg{std::string("fail") + std::to_string(i) + ".svg"};
+            svg.draw(tests[i].first, "blue");
+            svg.draw(tests[i].second, "green");
+            svg.Close();
+
+            // std::cout << std::endl;
+        }
+        REQUIRE(results[i] == expects[i]);
+    }
+    std::cout << std::endl;
+
+}
+
+struct Pair
+{
+    size_t first, second;
+    bool operator==(const Pair &b) const { return first == b.first && second == b.second; }
+};
+
+template<> struct std::hash<Pair> {
+    size_t operator()(const Pair &c) const
+    {
+        return c.first * PRINTER_PART_POLYGONS.size() + c.second;
+    }
+};
+
+TEST_CASE("Convex polygon intersection test prusa polygons", "[Geometry][Rotcalip]") {
+    std::unordered_set<Pair> combos;
+    for (size_t i = 0; i < PRINTER_PART_POLYGONS.size(); ++i) {
+        for (size_t j = 0; j < PRINTER_PART_POLYGONS.size(); ++j) {
+            if (i != j) {
+                size_t a = std::min(i, j), b = std::max(i, j);
+                combos.insert(Pair{a, b});
+            }
+        }
+    }
+
+    // All disjoint
+    for (const auto &combo : combos) {
+        Polygon A = PRINTER_PART_POLYGONS[combo.first], B = PRINTER_PART_POLYGONS[combo.second];
+        A = Geometry::convex_hull(A.points);
+        B = Geometry::convex_hull(B.points);
+
+        auto bba = A.bounding_box();
+        auto bbb = B.bounding_box();
+
+        A.translate(-bba.center());
+        B.translate(-bbb.center());
+
+        B.translate(bba.size() + bbb.size());
+
+        bool res = Geometry::intersects(A, B);
+        bool ref = !intersection(A, B).empty();
+
+        if (res != ref) {
+            SVG svg{std::string("fail") + std::to_string(combo.first) + "_" + std::to_string(combo.second) + ".svg"};
+            svg.draw(A, "blue");
+            svg.draw(B, "green");
+            svg.Close();
+        }
+
+        REQUIRE(res == ref);
+    }
+
+    // All intersecting
+    for (const auto &combo : combos) {
+        Polygon A = PRINTER_PART_POLYGONS[combo.first], B = PRINTER_PART_POLYGONS[combo.second];
+        A = Geometry::convex_hull(A.points);
+        B = Geometry::convex_hull(B.points);
+
+        auto bba = A.bounding_box();
+        auto bbb = B.bounding_box();
+
+        A.translate(-bba.center());
+        B.translate(-bbb.center());
+
+        bool res = Geometry::intersects(A, B);
+        bool ref = !intersection(A, B).empty();
+
+        if (res != ref) {
+            SVG svg{std::string("fail") + std::to_string(combo.first) + "_" + std::to_string(combo.second) + ".svg"};
+            svg.draw(A, "blue");
+            svg.draw(B, "green");
+            svg.Close();
+        }
+
+        REQUIRE(res == ref);
+    }
+}

From 476b48ed111507c1f63a3c3bcebb3020fb73a771 Mon Sep 17 00:00:00 2001
From: Lukas Matena <lukasmatena@seznam.cz>
Date: Thu, 30 Sep 2021 16:49:00 +0200
Subject: [PATCH 2/3] Revert "Merge branch 'tm_convex_intersect_rotcalip'"

This reverts commit 627d8bcaefb3c46864324554f663403495d9ef43, reversing
changes made to 66d44627248b8e412143298541b5a8397f8a0af5.

The change breaks build on mac
---
 src/libslic3r/Geometry.cpp        | 211 +-----------------------------
 src/libslic3r/Geometry.hpp        |   2 -
 tests/libslic3r/CMakeLists.txt    |   1 -
 tests/libslic3r/test_geometry.cpp | 173 ------------------------
 4 files changed, 1 insertion(+), 386 deletions(-)

diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp
index bbb1f0d6b..321443204 100644
--- a/src/libslic3r/Geometry.cpp
+++ b/src/libslic3r/Geometry.cpp
@@ -20,26 +20,6 @@
 #include <boost/algorithm/string/split.hpp>
 #include <boost/log/trivial.hpp>
 
-#if defined(_MSC_VER) && defined(__clang__)
-#define BOOST_NO_CXX17_HDR_STRING_VIEW
-#endif
-
-#include <libslic3r/Int128.hpp>
-
-#include <boost/multiprecision/integer.hpp>
-
-namespace Slic3r {
-
-#if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__)
-using int128_t = boost::multiprecision::int128_t;
-#else
-using int128_t = __int128;
-#endif
-
-using int256_t = boost::multiprecision::int256_t;
-
-} // namespace Slic3r
-
 #ifdef SLIC3R_DEBUG
 #include "SVG.hpp"
 #endif
@@ -1563,193 +1543,4 @@ double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to)
     return (axis.z() < 0) ? -angle : angle;
 }
 
-namespace rotcalip {
-
-inline int128_t magnsq(const Point &p)
-{
-    return int128_t(p.x()) * p.x() + int64_t(p.y()) * p.y();
-}
-
-inline int128_t dot(const Point &a, const Point &b)
-{
-    return int128_t(a.x()) * b.x() + int64_t(a.y()) * b.y();
-}
-
-// Compares the angle enclosed by vectors dir and dirA (alpha) with the angle
-// enclosed by -dir and dirB (beta). Returns -1 if alpha is less than beta, 0
-// if they are equal and 1 if alpha is greater than beta. Note that dir is
-// reversed for beta, because it represents the opposite side of a caliper.
-int cmp_angles(const Point &dir, const Point &dirA, const Point &dirB) {
-    int128_t dotA = dot(dir, dirA);
-    int128_t dotB = dot(-dir, dirB);
-    int256_t dcosa = int256_t(magnsq(dirB)) * int256_t(std::abs(dotA)) * dotA;
-    int256_t dcosb = int256_t(magnsq(dirA)) * int256_t(std::abs(dotB)) * dotB;
-    int256_t diff = dcosa - dcosb;
-
-    return diff > 0? -1 : (diff < 0 ? 1 : 0);
-}
-
-// A helper class to navigate on a polygon. Given a vertex index, one can
-// get the edge belonging to that vertex, the coordinates of the vertex, the
-// next and previous edges. Stuff that is needed in the rotating calipers algo.
-class Idx
-{
-    size_t m_idx;
-    const Polygon *m_poly;
-public:
-    explicit Idx(const Polygon &p): m_idx{0}, m_poly{&p} {}
-    explicit Idx(size_t idx, const Polygon &p): m_idx{idx}, m_poly{&p} {}
-
-    size_t idx() const { return m_idx; }
-    void set_idx(size_t i) { m_idx = i; }
-    size_t next() const { return (m_idx + 1) % m_poly->size(); }
-    size_t inc() { return m_idx = (m_idx + 1) % m_poly->size(); }
-    Point prev_dir() const {
-        return pt() - (*m_poly)[(m_idx + m_poly->size() - 1) % m_poly->size()];
-    }
-
-    const Point &pt() const { return (*m_poly)[m_idx]; }
-    const Point dir() const { return (*m_poly)[next()] - pt(); }
-    const Point  next_dir() const
-    {
-        return (*m_poly)[(m_idx + 2) % m_poly->size()] - (*m_poly)[next()];
-    }
-    const Polygon &poly() const { return *m_poly; }
-};
-
-enum class AntipodalVisitMode { Full, SkipParallelSegments };
-
-// Visit all antipodal pairs starting from the initial ia, ib pair which
-// has to be a valid antipodal pair (not checked). fn is called for every
-// antipodal pair encountered including the initial one.
-// The callback Fn has a signiture of bool(size_t i, size_t j, const Point &dir)
-// where i,j are the vertex indices of the antipodal pair and dir is the
-// direction of the calipers touching the i vertex.
-template<AntipodalVisitMode mode = AntipodalVisitMode::Full, class Fn>
-void visit_antipodals (Idx& ia, Idx &ib, Fn &&fn)
-{
-    // Set current caliper direction to be the lower edge angle from X axis
-    int cmp = cmp_angles(ia.prev_dir(), ia.dir(), ib.dir());
-    Idx *current = cmp <= 0 ? &ia : &ib, *other = cmp <= 0 ? &ib : &ia;
-    bool visitor_continue = true;
-
-    size_t a_start = ia.idx(), b_start = ib.idx();
-    bool a_finished = false, b_finished = false;
-
-    while (visitor_continue && !(a_finished && b_finished)) {
-        Point current_dir_a = current == &ia ? current->dir() : -current->dir();
-        visitor_continue = fn(ia.idx(), ib.idx(), current_dir_a);
-
-        if constexpr (mode == AntipodalVisitMode::Full)
-            if (cmp == 0 && visitor_continue) {
-                visitor_continue = fn(current == &ia ? ia.idx() : ia.next(),
-                                      current == &ib ? ib.idx() : ib.next(),
-                                      current_dir_a);
-            }
-
-        cmp = cmp_angles(current->dir(), current->next_dir(), other->dir());
-
-        current->inc();
-        if (cmp > 0) {
-            std::swap(current, other);
-        }
-
-        if (ia.idx() == a_start) a_finished = true;
-        if (ib.idx() == b_start) b_finished = true;
-    }
-}
-
-static bool is_left(const Point &a, const Point &b, const Point &c)
-{
-    Vec<2, int64_t> V = (b - a).cast<int64_t>();
-    Vec<2, int64_t> W = (c - a).cast<int64_t>();
-
-    return V.x() * W.y() - V.y() * W.x() > 0;
-}
-
-} // namespace rotcalip
-
-bool intersects(const Polygon &A, const Polygon &B)
-{
-    using namespace rotcalip;
-
-    // Establish starting antipodals as extremes in XY plane. Use the
-    // easily obtainable bounding boxes to check if A and B is disjoint
-    // and return false if the are.
-
-    struct BB
-    {
-        size_t         xmin = 0, xmax = 0, ymin = 0, ymax = 0;
-        const Polygon &P;
-        static bool cmpy(const Point &l, const Point &u)
-        {
-            return l.y() < u.y() || (l.y() == u.y() && l.x() < u.x());
-        }
-
-        BB(const Polygon &poly): P{poly}
-        {
-            for (size_t i = 0; i < P.size(); ++i) {
-                if (P[i] < P[xmin]) xmin = i;
-                if (P[xmax] < P[i]) xmax = i;
-                if (cmpy(P[i], P[ymin])) ymin = i;
-                if (cmpy(P[ymax], P[i])) ymax = i;
-            }
-        }
-    };
-
-    BB bA{A}, bB{B};
-    BoundingBox bbA{{A[bA.xmin].x(), A[bA.ymin].y()}, {A[bA.xmax].x(), A[bA.ymax].y()}};
-    BoundingBox bbB{{B[bB.xmin].x(), B[bB.ymin].y()}, {B[bB.xmax].x(), B[bB.ymax].y()}};
-
-    if (!bbA.overlap(bbB))
-        return false;
-
-    // Establish starting antipodals as extreme vertex pairs in X or Y direction
-    // which reside on different polygons. If no such pair is found, the two
-    // polygons are certainly not disjoint.
-    Idx imin{bA.xmin, A}, imax{bB.xmax, B};
-    if (B[bB.xmin] < imin.pt())  imin = Idx{bB.xmin, B};
-    if (imax.pt()  < A[bA.xmax]) imax = Idx{bA.xmax, A};
-    if (&imin.poly() == &imax.poly()) {
-        imin = Idx{bA.ymin, A};
-        imax = Idx{bB.ymax, B};
-        if (B[bB.ymin] < imin.pt())  imin = Idx{bB.ymin, B};
-        if (imax.pt()  < A[bA.ymax]) imax = Idx{bA.ymax, A};
-    }
-
-    if (&imin.poly() == &imax.poly())
-        return true;
-
-    bool found_divisor;
-    visit_antipodals<AntipodalVisitMode::SkipParallelSegments>(
-        imin, imax,
-        [&imin, &imax, &found_divisor](size_t ia, size_t ib, const Point &dir) {
-            //        std::cout << "A" << ia << " B" << ib << " dir " <<
-            //        dir.x() << " " << dir.y() << std::endl;
-            const Polygon &A = imin.poly(), &B = imax.poly();
-
-            Point ref_a     = A[(ia + 2) % A.size()],
-                  ref_b     = B[(ib + 2) % B.size()];
-            Point Anext     = A[ia] + dir;
-            bool  is_left_a = is_left(A[ia], Anext, ref_a);
-            bool  is_left_b = is_left(B[ib], B[ib] - dir, ref_b);
-
-            // If both reference points are on the left (or right) of the
-            // support line and the opposite support line is to the righ (or
-            // left), the divisor line is found. We only test the reference
-            // point, as by definition, if that is on one side, all the other
-            // points must be on the same side of a support line.
-            if (is_left(A[ia], Anext, B[ib])) {
-                found_divisor = !is_left_a && !is_left_b;
-            } else {
-                found_divisor = is_left_a && is_left_b;
-            }
-
-            return !found_divisor;
-        });
-
-    // Intersects if the divisor was not found
-    return !found_divisor;
-}
-
-}} // namespace Slic3r::Geometry
+} }
diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp
index bb583c33c..c6af515c8 100644
--- a/src/libslic3r/Geometry.hpp
+++ b/src/libslic3r/Geometry.hpp
@@ -532,8 +532,6 @@ inline bool is_rotation_ninety_degrees(const Vec3d &rotation)
     return is_rotation_ninety_degrees(rotation.x()) && is_rotation_ninety_degrees(rotation.y()) && is_rotation_ninety_degrees(rotation.z());
 }
 
-bool intersects(const Polygon &convex_poly1, const Polygon &convex_poly2);
-
 } } // namespace Slicer::Geometry
 
 #endif
diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt
index 05898db28..575878cf2 100644
--- a/tests/libslic3r/CMakeLists.txt
+++ b/tests/libslic3r/CMakeLists.txt
@@ -23,7 +23,6 @@ add_executable(${_TEST_NAME}_tests
     test_png_io.cpp
     test_timeutils.cpp
     test_indexed_triangle_set.cpp
-    ../libnest2d/printer_parts.cpp
 	)
 
 if (TARGET OpenVDB::openvdb)
diff --git a/tests/libslic3r/test_geometry.cpp b/tests/libslic3r/test_geometry.cpp
index 7373eaa6a..24e0908cc 100644
--- a/tests/libslic3r/test_geometry.cpp
+++ b/tests/libslic3r/test_geometry.cpp
@@ -9,14 +9,6 @@
 #include "libslic3r/ClipperUtils.hpp"
 #include "libslic3r/ShortestPath.hpp"
 
-#include <random>
-#include "libnest2d/tools/benchmark.h"
-#include "libslic3r/SVG.hpp"
-
-#include "../libnest2d/printer_parts.hpp"
-
-#include <unordered_set>
-
 using namespace Slic3r;
 
 TEST_CASE("Polygon::contains works properly", "[Geometry]"){
@@ -460,168 +452,3 @@ SCENARIO("Ported from xs/t/14_geometry.t", "[Geometry]"){
     	REQUIRE(! Slic3r::Geometry::directions_parallel(M_PI /2, PI, M_PI /180));
     }
 }
-
-static Polygon gen_convex_poly(std::mt19937_64 &rg, size_t point_cnt)
-{
-    std::uniform_int_distribution<coord_t> dist(0, 100);
-
-    Polygon out;
-    out.points.reserve(point_cnt);
-
-    coord_t tr = dist(rg) * 2 / SCALING_FACTOR;
-
-    for (size_t i = 0; i < point_cnt; ++i)
-        out.points.emplace_back(tr + dist(rg) / SCALING_FACTOR,
-                                tr + dist(rg) / SCALING_FACTOR);
-
-    return Geometry::convex_hull(out.points);
-}
-
-TEST_CASE("Convex polygon intersection on two disjoint squares", "[Geometry][Rotcalip]") {
-    Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
-    A.scale(1. / SCALING_FACTOR);
-
-    Polygon B = A;
-    B.translate(20 / SCALING_FACTOR, 0);
-
-    bool is_inters = Geometry::intersects(A, B);
-
-    REQUIRE(is_inters != true);
-}
-
-TEST_CASE("Convex polygon intersection on two intersecting squares", "[Geometry][Rotcalip]") {
-    Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
-    A.scale(1. / SCALING_FACTOR);
-
-    Polygon B = A;
-    B.translate(5 / SCALING_FACTOR, 5 / SCALING_FACTOR);
-
-    bool is_inters = Geometry::intersects(A, B);
-
-    REQUIRE(is_inters == true);
-}
-
-TEST_CASE("Convex polygon intersection test on random polygons", "[Geometry]") {
-    constexpr size_t TEST_CNT = 1000;
-    constexpr size_t POINT_CNT = 1000;
-
-    std::mt19937_64 rg{std::random_device{}()};
-    Benchmark bench;
-
-    auto tests = reserve_vector<std::pair<Polygon, Polygon>>(TEST_CNT);
-    auto results = reserve_vector<bool>(TEST_CNT);
-    auto expects = reserve_vector<bool>(TEST_CNT);
-
-    for (size_t i = 0; i < TEST_CNT; ++i) {
-        tests.emplace_back(gen_convex_poly(rg, POINT_CNT), gen_convex_poly(rg, POINT_CNT));
-    }
-
-    bench.start();
-    for (const auto &test : tests)
-        results.emplace_back(Geometry::intersects(test.first, test.second));
-    bench.stop();
-
-    std::cout << "Test time: " << bench.getElapsedSec() << std::endl;
-
-    bench.start();
-    for (const auto &test : tests)
-        expects.emplace_back(!intersection(test.first, test.second).empty());
-    bench.stop();
-
-    std::cout << "Clipper time: " << bench.getElapsedSec() << std::endl;
-
-    REQUIRE(results.size() == expects.size());
-
-    for (size_t i = 0; i < results.size(); ++i) {
-        // std::cout << expects[i] << " ";
-
-        if (results[i] != expects[i]) {
-            SVG svg{std::string("fail") + std::to_string(i) + ".svg"};
-            svg.draw(tests[i].first, "blue");
-            svg.draw(tests[i].second, "green");
-            svg.Close();
-
-            // std::cout << std::endl;
-        }
-        REQUIRE(results[i] == expects[i]);
-    }
-    std::cout << std::endl;
-
-}
-
-struct Pair
-{
-    size_t first, second;
-    bool operator==(const Pair &b) const { return first == b.first && second == b.second; }
-};
-
-template<> struct std::hash<Pair> {
-    size_t operator()(const Pair &c) const
-    {
-        return c.first * PRINTER_PART_POLYGONS.size() + c.second;
-    }
-};
-
-TEST_CASE("Convex polygon intersection test prusa polygons", "[Geometry][Rotcalip]") {
-    std::unordered_set<Pair> combos;
-    for (size_t i = 0; i < PRINTER_PART_POLYGONS.size(); ++i) {
-        for (size_t j = 0; j < PRINTER_PART_POLYGONS.size(); ++j) {
-            if (i != j) {
-                size_t a = std::min(i, j), b = std::max(i, j);
-                combos.insert(Pair{a, b});
-            }
-        }
-    }
-
-    // All disjoint
-    for (const auto &combo : combos) {
-        Polygon A = PRINTER_PART_POLYGONS[combo.first], B = PRINTER_PART_POLYGONS[combo.second];
-        A = Geometry::convex_hull(A.points);
-        B = Geometry::convex_hull(B.points);
-
-        auto bba = A.bounding_box();
-        auto bbb = B.bounding_box();
-
-        A.translate(-bba.center());
-        B.translate(-bbb.center());
-
-        B.translate(bba.size() + bbb.size());
-
-        bool res = Geometry::intersects(A, B);
-        bool ref = !intersection(A, B).empty();
-
-        if (res != ref) {
-            SVG svg{std::string("fail") + std::to_string(combo.first) + "_" + std::to_string(combo.second) + ".svg"};
-            svg.draw(A, "blue");
-            svg.draw(B, "green");
-            svg.Close();
-        }
-
-        REQUIRE(res == ref);
-    }
-
-    // All intersecting
-    for (const auto &combo : combos) {
-        Polygon A = PRINTER_PART_POLYGONS[combo.first], B = PRINTER_PART_POLYGONS[combo.second];
-        A = Geometry::convex_hull(A.points);
-        B = Geometry::convex_hull(B.points);
-
-        auto bba = A.bounding_box();
-        auto bbb = B.bounding_box();
-
-        A.translate(-bba.center());
-        B.translate(-bbb.center());
-
-        bool res = Geometry::intersects(A, B);
-        bool ref = !intersection(A, B).empty();
-
-        if (res != ref) {
-            SVG svg{std::string("fail") + std::to_string(combo.first) + "_" + std::to_string(combo.second) + ".svg"};
-            svg.draw(A, "blue");
-            svg.draw(B, "green");
-            svg.Close();
-        }
-
-        REQUIRE(res == ref);
-    }
-}

From 5048dbf8b473f4ee4200a1410e59f4fae78c3ce8 Mon Sep 17 00:00:00 2001
From: Lukas Matena <lukasmatena@seznam.cz>
Date: Thu, 30 Sep 2021 16:50:50 +0200
Subject: [PATCH 3/3] Bumped up version number

---
 version.inc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/version.inc b/version.inc
index b7e941bb4..fdbc2523f 100644
--- a/version.inc
+++ b/version.inc
@@ -3,7 +3,7 @@
 
 set(SLIC3R_APP_NAME "PrusaSlicer")
 set(SLIC3R_APP_KEY "PrusaSlicer")
-set(SLIC3R_VERSION "2.4.0-alpha2")
+set(SLIC3R_VERSION "2.4.0-alpha3")
 set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN")
 set(SLIC3R_RC_VERSION "2,4,0,0")
 set(SLIC3R_RC_VERSION_DOTS "2.4.0.0")