diff --git a/Partition_2/include/CGAL/Partition_2/partition_optimal_convex_2.h b/Partition_2/include/CGAL/Partition_2/partition_optimal_convex_2.h index f5f500720a4e..e65972d9abf8 100644 --- a/Partition_2/include/CGAL/Partition_2/partition_optimal_convex_2.h +++ b/Partition_2/include/CGAL/Partition_2/partition_optimal_convex_2.h @@ -69,6 +69,7 @@ #include #include #include +#include #include #include #include @@ -523,6 +524,30 @@ OutputIterator partition_optimal_convex_2(InputIterator first, CGAL_precondition( orientation_2(polygon.begin(), polygon.end(), traits) == COUNTERCLOCKWISE); + // The algorithm is sensitive to the choice of starting vertex (vertex 0). + // When vertex 0 is a non-convex or "awkward" vertex the closing boundary + // edge (n-1 -> 0) is mis-classified during preprocessing, which can cause + // non-simple or non-convex pieces to be returned. Normalising the polygon + // so that vertex 0 is the lexicographically smallest vertex guarantees it + // is a convex vertex (true for any simple polygon), satisfying the + // algorithm's anchor assumption. See https://github.com/CGAL/cgal/issues/9322. + // + // Note: std::rotate is safe here because diagonal lists are not yet + // populated (that happens in partition_opt_cvx_preprocessing below). + { + typedef typename Traits::Less_xy_2 Less_xy_2; + Less_xy_2 less_xy = traits.less_xy_2_object(); + typename P_Polygon_2::iterator min_it = polygon.begin(); + for (typename P_Polygon_2::iterator it = std::next(polygon.begin()); + it != polygon.end(); ++it) + { + if (less_xy(static_cast(*it), + static_cast(*min_it))) + min_it = it; + } + std::rotate(polygon.begin(), min_it, polygon.end()); + } + #ifdef CGAL_PARTITION_OPTIMAL_CONVEX_DEBUG std::cout << "The polygon: " << std::endl; for (S i = 0; i < polygon.size(); i++) diff --git a/Partition_2/test/Partition_2/convex_test_polys.h b/Partition_2/test/Partition_2/convex_test_polys.h index 359cb8934c82..ea2090a56c4a 100644 --- a/Partition_2/test/Partition_2/convex_test_polys.h +++ b/Partition_2/test/Partition_2/convex_test_polys.h @@ -23,6 +23,27 @@ void make_convex_w_collinear_points(Polygon_2& polygon) } +// Cup-shaped octagon from issue #9322: +// optimal_convex_partition_2 produced non-simple / non-convex pieces when +// the polygon was supplied with certain vertices as vertex 0. +// The three originally failing rotations have vertex 0 at +// (4,4), (6,6), and (0,6) respectively (rotations 0, 1, 2). +template +void make_cup_octagon_issue_9322(Polygon_2& polygon, int rotation = 0) +{ + typedef typename Polygon_2::Point_2 Point_2; + // CCW cup-shaped octagon (verified by positive shoelace area when + // starting at (4,4)). + const int n = 8; + Point_2 verts[n] = { + Point_2(4, 4), Point_2(6, 6), Point_2(0, 6), + Point_2(2, 4), Point_2(2, 2), Point_2(0, 0), + Point_2(6, 0), Point_2(4, 2) + }; + for (int i = 0; i < n; ++i) + polygon.push_back(verts[(i + rotation) % n]); +} + template void make_monotone_convex(Polygon_2& polygon) { diff --git a/Partition_2/test/Partition_2/test_optimal_convex.h b/Partition_2/test/Partition_2/test_optimal_convex.h index b7a2281715d1..c7062c8719fb 100644 --- a/Partition_2/test/Partition_2/test_optimal_convex.h +++ b/Partition_2/test/Partition_2/test_optimal_convex.h @@ -1,4 +1,16 @@ +// Verify that every piece in the partition is simple and convex. +static bool partition_is_valid(const std::list& pieces) +{ + for (const Polygon_2& p : pieces) + { + if (!p.is_simple()) return false; + if (!CGAL::is_convex_2(p.vertices_begin(), p.vertices_end())) + return false; + } + return true; +} + void test_optimal_convex() { Polygon_2 polygon; @@ -35,6 +47,22 @@ void test_optimal_convex() CGAL::optimal_convex_partition_2(polygon.vertices_begin(), polygon.vertices_end(), std::back_inserter(partition_polys)); + + // Regression test for https://github.com/CGAL/cgal/issues/9322: + // optimal_convex_partition_2 produced non-simple / non-convex pieces for + // a cup-shaped octagon when certain vertices were used as the start vertex. + // Rotations 0 (start at (4,4)), 1 (start at (6,6)), and 2 (start at (0,6)) + // were the originally failing cases. Test all 8 rotations to be thorough. + for (int rot = 0; rot < 8; ++rot) + { + partition_polys.clear(); + polygon.erase(polygon.vertices_begin(), polygon.vertices_end()); + make_cup_octagon_issue_9322(polygon, rot); + CGAL::optimal_convex_partition_2(polygon.vertices_begin(), + polygon.vertices_end(), + std::back_inserter(partition_polys)); + assert(partition_is_valid(partition_polys)); + } /* partition_polys.clear(); polygon.erase(polygon.vertices_begin(), polygon.vertices_end());