Skip to content

Commit

Permalink
Fix some convex hull related issues
Browse files Browse the repository at this point in the history
Previously we tried to be cheeky and cut some corners by implementing “Andrew’s Monotone Chain Convex Hull Algorithm” without sorting points. However, turns out we need to sort as we did run into some edge cases where this wouldn't work.

CURA-11395
  • Loading branch information
casperlamboo committed Jan 10, 2024
1 parent 25870b8 commit 07c6a2b
Showing 1 changed file with 35 additions and 25 deletions.
60 changes: 35 additions & 25 deletions src/utils/polygon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <range/v3/view/c_str.hpp>
#include <range/v3/view/filter.hpp>
#include <range/v3/view/join.hpp>
#include <range/v3/view/sliding.hpp>
#include <range/v3/view/transform.hpp>
#include <range/v3/view/zip.hpp>

Expand Down Expand Up @@ -104,43 +105,52 @@ Polygons Polygons::approxConvexHull(int extra_outset)

void Polygons::makeConvex()
{
if (this->paths.empty())
{
// early out if there are no polygons
return;
}

// convex hulls don't have holes so resize paths to only contain the outer path
this->paths.resize(1);

for (PolygonRef poly : *this)
{
if (poly.size() <= 3)
{
continue; // Already convex.
// Already convex.
continue;
}

Polygon convexified;

// Start from a vertex that is known to be on the convex hull: The one with the lowest X.
const size_t start_index = std::min_element(
poly.begin(),
poly.end(),
[](Point2LL a, Point2LL b)
{
return a.X == b.X ? a.Y < b.Y : a.X < b.X;
})
- poly.begin();
convexified.path->push_back(poly[start_index]);

for (size_t i = 1; i <= poly.size(); ++i)
auto makeSortedPolyConvex = [&convexified](PolygonRef& poly)
{
const Point2LL& current = poly[(start_index + i) % poly.size()];
convexified.path->push_back(poly[0]);

// Track backwards to make sure we haven't been in a concave pocket for multiple vertices already.
while (convexified.size() >= 2
&& (LinearAlg2D::pointIsLeftOfLine(convexified.path->back(), (*convexified.path)[convexified.size() - 2], current) >= 0
|| LinearAlg2D::pointIsLeftOfLine(convexified.path->back(), (*convexified.path)[convexified.size() - 2], convexified.path->front()) > 0))
for (const auto window : poly | ranges::views::sliding(2))
{
convexified.path->pop_back();
const Point2LL& current = window[0];
const Point2LL& after = window[1];

if (LinearAlg2D::pointIsLeftOfLine(current, convexified.path->back(), after) < 0)
{
//Track backwards to make sure we haven't been in a concave pocket for multiple vertices already.
while(convexified.size() >= 2 && LinearAlg2D::pointIsLeftOfLine(convexified.path->back(), (*convexified.path)[convexified.size() - 2], current) > 0)
{
convexified.path->pop_back();
}
convexified.path->push_back(current);
}
}
convexified.path->push_back(current);
}
// remove last vertex as the starting vertex is added in the last iteration of the loop
convexified.path->pop_back();
};

std::sort(poly.begin(), poly.end(), [](Point2LL a, Point2LL b) { return a.X == b.X ? a.Y < b.Y : a.X < b.X; });
makeSortedPolyConvex(poly);
std::reverse(poly.begin(), poly.end());
makeSortedPolyConvex(poly);

poly.path->swap(*convexified.path); // Due to vector's implementation, this is constant time.
// Due to vector's implementation, this is constant time
poly.path->swap(*convexified.path);
}
}

Expand Down

0 comments on commit 07c6a2b

Please sign in to comment.