Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Partition_2/include/CGAL/Partition_2/partition_optimal_convex_2.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
#include<CGAL/Partition_traits_2.h>
#include<CGAL/assertions.h>
#include<CGAL/Partition_2/Vertex_visibility_graph_2.h>
#include<algorithm>
#include<utility>
#include<vector>
#include<iterator>
Expand Down Expand Up @@ -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<typename Traits::Point_2>(*it),
static_cast<typename Traits::Point_2>(*min_it)))
min_it = it;
}
std::rotate(polygon.begin(), min_it, polygon.end());
}
Comment on lines +541 to +549
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::rotate is used here, but this header does not include <algorithm>. Relying on transitive includes is non-portable and can break compilation on some standard library implementations; please add the proper include in this header.

Copilot uses AI. Check for mistakes.

#ifdef CGAL_PARTITION_OPTIMAL_CONVEX_DEBUG
std::cout << "The polygon: " << std::endl;
for (S i = 0; i < polygon.size(); i++)
Expand Down
21 changes: 21 additions & 0 deletions Partition_2/test/Partition_2/convex_test_polys.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <class Polygon_2>
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 <class Polygon_2>
void make_monotone_convex(Polygon_2& polygon)
{
Expand Down
28 changes: 28 additions & 0 deletions Partition_2/test/Partition_2/test_optimal_convex.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@

// Verify that every piece in the partition is simple and convex.
static bool partition_is_valid(const std::list<Polygon_2>& 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;
Expand Down Expand Up @@ -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());
Expand Down