Reworked the fix of #784 for efficiency and robustness:

First, the same direction segments are chained as before, but this time
the remaining open polylines are collected to be processed in the 2nd step.

Second, the remaining open polylines are connected by a greedy algorithm
disregarding their original orientation. As the orientation of loops
created by the 2nd step is mixed, the orientation of these loops is
unknown, therfore a CCW orientation is enforced. The CCW heuristics
may fill holes and cavities, but no outer geometry will be lost.
This commit is contained in:
bubnikv 2018-03-15 17:14:13 +01:00
parent 1ae8684af1
commit 4f0c6dd879
2 changed files with 219 additions and 143 deletions

View File

@ -988,39 +988,41 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
}
}
struct OpenPolyline {
OpenPolyline() {};
OpenPolyline(IntersectionReference &start, IntersectionReference &end, Points &&points) :
start(start), end(end), points(std::move(points)), consumed(false) {}
void reverse() {
std::swap(start, end);
std::reverse(points.begin(), points.end());
}
IntersectionReference start;
IntersectionReference end;
Points points;
bool consumed;
};
std::vector<OpenPolyline> open_polylines;
{
// Build a map of lines by edge_a_id and a_id.
std::vector<IntersectionLine*> by_edge_a_id;
std::vector<IntersectionLine*> by_edge_b_id;
std::vector<IntersectionLine*> by_a_id;
std::vector<IntersectionLine*> by_b_id;
by_edge_a_id.reserve(lines.size());
by_edge_b_id.reserve(lines.size());
by_a_id.reserve(lines.size());
by_b_id.reserve(lines.size());
for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++ line) {
if (! line->skip) {
if (line->edge_a_id != -1)
by_edge_a_id.push_back(&(*line)); // [line->edge_a_id].push_back();
if (line->edge_b_id != -1)
by_edge_b_id.push_back(&(*line));
if (line->a_id != -1)
by_a_id.push_back(&(*line)); // [line->a_id].push_back(&(*line));
if (line->b_id != -1)
by_b_id.push_back(&(*line)); // [line->a_id].push_back(&(*line));
for (IntersectionLine &line : lines) {
if (! line.skip) {
if (line.edge_a_id != -1)
by_edge_a_id.emplace_back(&line);
if (line.a_id != -1)
by_a_id.emplace_back(&line);
}
}
auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; };
auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; };
auto by_edge_b_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_b_id < il2->edge_b_id; };
auto by_vertex_b_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->b_id < il2->b_id; };
std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower);
std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower);
std::sort(by_edge_b_id.begin(), by_edge_b_id.end(), by_edge_b_lower);
std::sort(by_b_id.begin(), by_b_id.end(), by_vertex_b_lower);
// Chain the segments with a greedy algorithm, collect the loops and unclosed polylines.
IntersectionLines::iterator it_line_seed = lines.begin();
CYCLE: while (1) {
for (;;) {
// take first spare line and start a new loop
IntersectionLine *first_line = nullptr;
for (; it_line_seed != lines.end(); ++ it_line_seed)
@ -1032,7 +1034,7 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
break;
first_line->skip = true;
Points loop_pts;
loop_pts.push_back(first_line->a);
loop_pts.emplace_back(first_line->a);
IntersectionLine *last_line = first_line;
/*
@ -1056,29 +1058,6 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
break;
}
}
if (next_line == nullptr)
{
// checks reverted edge
key.edge_b_id = last_line->edge_b_id;
auto it_begin = std::lower_bound(by_edge_b_id.begin(), by_edge_b_id.end(), &key, by_edge_b_lower);
if (it_begin != by_edge_b_id.end()) {
auto it_end = std::upper_bound(it_begin, by_edge_b_id.end(), &key, by_edge_b_lower);
for (auto it_line = it_begin; it_line != it_end; ++it_line)
if (!(*it_line)->skip) {
// reverts edge
std::swap((*it_line)->a, (*it_line)->b);
std::swap((*it_line)->a_id, (*it_line)->b_id);
std::swap((*it_line)->edge_a_id, (*it_line)->edge_b_id);
next_line = *it_line;
// resorts lists
std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower);
std::sort(by_edge_b_id.begin(), by_edge_b_id.end(), by_edge_b_lower);
std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower);
std::sort(by_b_id.begin(), by_b_id.end(), by_vertex_b_lower);
break;
}
}
}
}
if (next_line == nullptr && last_line->b_id != -1) {
key.a_id = last_line->b_id;
@ -1091,58 +1070,146 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
break;
}
}
if (next_line == nullptr)
{
// checks reverted edge
key.b_id = last_line->b_id;
auto it_begin = std::lower_bound(by_b_id.begin(), by_b_id.end(), &key, by_vertex_b_lower);
if (it_begin != by_b_id.end()) {
auto it_end = std::upper_bound(it_begin, by_b_id.end(), &key, by_vertex_b_lower);
for (auto it_line = it_begin; it_line != it_end; ++it_line)
if (!(*it_line)->skip) {
// reverts edge
std::swap((*it_line)->a, (*it_line)->b);
std::swap((*it_line)->a_id, (*it_line)->b_id);
std::swap((*it_line)->edge_a_id, (*it_line)->edge_b_id);
next_line = *it_line;
// resorts lists
std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower);
std::sort(by_edge_b_id.begin(), by_edge_b_id.end(), by_edge_b_lower);
std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower);
std::sort(by_b_id.begin(), by_b_id.end(), by_vertex_b_lower);
break;
}
}
}
}
if (next_line == nullptr) {
// check whether we closed this loop
// Check whether we closed this loop.
if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) ||
(first_line->a_id != -1 && first_line->a_id == last_line->b_id)) {
// loop is complete
// The current loop is complete. Add it to the output.
loops->emplace_back(std::move(loop_pts));
#ifdef SLIC3R_TRIANGLEMESH_DEBUG
printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size());
#endif
goto CYCLE;
} else {
// This is an open polyline. Add it to the list of open polylines. These open polylines will processed later.
loop_pts.emplace_back(last_line->b);
open_polylines.emplace_back(OpenPolyline(
IntersectionReference(first_line->a_id, first_line->edge_a_id),
IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts)));
}
// we can't close this loop!
//// push @failed_loops, [@loop];
//#ifdef SLIC3R_TRIANGLEMESH_DEBUG
printf(" Unable to close this loop having %d points\n", (int)loop_pts.size());
//#endif
goto CYCLE;
break;
}
/*
printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n",
next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id,
next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y);
*/
loop_pts.push_back(next_line->a);
loop_pts.emplace_back(next_line->a);
last_line = next_line;
next_line->skip = true;
}
}
}
// Now process the open polylines.
if (! open_polylines.empty()) {
// Store the end points of open_polylines into vectors sorted
struct OpenPolylineEnd {
OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {}
OpenPolyline *polyline;
// Is it the start or end point?
bool start;
const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; }
int point_id() const { return ipref().point_id; }
int edge_id () const { return ipref().edge_id; }
};
auto by_edge_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.edge_id() < ope2.edge_id(); };
auto by_point_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.point_id() < ope2.point_id(); };
std::vector<OpenPolylineEnd> by_edge_id;
std::vector<OpenPolylineEnd> by_point_id;
by_edge_id.reserve(2 * open_polylines.size());
by_point_id.reserve(2 * open_polylines.size());
for (OpenPolyline &opl : open_polylines) {
if (opl.start.edge_id != -1)
by_edge_id .emplace_back(OpenPolylineEnd(&opl, true));
if (opl.end.edge_id != -1)
by_edge_id .emplace_back(OpenPolylineEnd(&opl, false));
if (opl.start.point_id != -1)
by_point_id.emplace_back(OpenPolylineEnd(&opl, true));
if (opl.end.point_id != -1)
by_point_id.emplace_back(OpenPolylineEnd(&opl, false));
}
std::sort(by_edge_id .begin(), by_edge_id .end(), by_edge_lower);
std::sort(by_point_id.begin(), by_point_id.end(), by_point_lower);
// Try to connect the loops.
for (OpenPolyline &opl : open_polylines) {
if (opl.consumed)
continue;
opl.consumed = true;
OpenPolylineEnd end(&opl, false);
for (;;) {
// find a line starting where last one finishes
OpenPolylineEnd* next_start = nullptr;
if (end.edge_id() != -1) {
auto it_begin = std::lower_bound(by_edge_id.begin(), by_edge_id.end(), end, by_edge_lower);
if (it_begin != by_edge_id.end()) {
auto it_end = std::upper_bound(it_begin, by_edge_id.end(), end, by_edge_lower);
for (auto it_edge = it_begin; it_edge != it_end; ++ it_edge)
if (! it_edge->polyline->consumed) {
next_start = &(*it_edge);
break;
}
}
}
if (next_start == nullptr && end.point_id() != -1) {
auto it_begin = std::lower_bound(by_point_id.begin(), by_point_id.end(), end, by_point_lower);
if (it_begin != by_point_id.end()) {
auto it_end = std::upper_bound(it_begin, by_point_id.end(), end, by_point_lower);
for (auto it_point = it_begin; it_point != it_end; ++ it_point)
if (! it_point->polyline->consumed) {
next_start = &(*it_point);
break;
}
}
}
if (next_start == nullptr) {
// The current loop could not be closed. Unmark the segment.
opl.consumed = false;
break;
}
// Attach this polyline to the end of the initial polyline.
if (next_start->start) {
auto it = next_start->polyline->points.begin();
std::copy(++ it, next_start->polyline->points.end(), back_inserter(opl.points));
//opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.end());
} else {
auto it = next_start->polyline->points.rbegin();
std::copy(++ it, next_start->polyline->points.rend(), back_inserter(opl.points));
//opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.rend());
}
end = *next_start;
end.start = !end.start;
next_start->polyline->points.clear();
next_start->polyline->consumed = true;
// Check whether we closed this loop.
const IntersectionReference &ip1 = opl.start;
const IntersectionReference &ip2 = end.ipref();
if ((ip1.edge_id != -1 && ip1.edge_id == ip2.edge_id) ||
(ip1.point_id != -1 && ip1.point_id == ip2.point_id)) {
// The current loop is complete. Add it to the output.
assert(opl.points.front().point_id == opl.points.back().point_id);
assert(opl.points.front().edge_id == opl.points.back().edge_id);
// Remove the duplicate last point.
opl.points.pop_back();
if (opl.points.size() >= 3) {
// The closed polygon is patched from pieces with messed up orientation, therefore
// the orientation of the patched up polygon is not known.
// Orient the patched up polygons CCW. This heuristic may close some holes and cavities.
double area = 0.;
for (size_t i = 0, j = opl.points.size() - 1; i < opl.points.size(); j = i ++)
area += double(opl.points[j].x + opl.points[i].x) * double(opl.points[i].y - opl.points[j].y);
if (area < 0)
std::reverse(opl.points.begin(), opl.points.end());
loops->emplace_back(std::move(opl.points));
}
opl.points.clear();
break;
}
// Continue with the current loop.
}
}
}
}
// Only used to cut the mesh into two halves.

View File

@ -85,11 +85,11 @@ enum FacetEdgeType {
feHorizontal
};
class IntersectionPoint : public Point
class IntersectionReference
{
public:
IntersectionPoint() : point_id(-1), edge_id(-1) {};
// Inherits coord_t x, y
IntersectionReference() : point_id(-1), edge_id(-1) {};
IntersectionReference(int point_id, int edge_id) : point_id(point_id), edge_id(edge_id) {}
// Where is this intersection point located? On mesh vertex or mesh edge?
// Only one of the following will be set, the other will remain set to -1.
// Index of the mesh vertex.
@ -98,6 +98,15 @@ public:
int edge_id;
};
class IntersectionPoint : public Point, public IntersectionReference
{
public:
IntersectionPoint() {};
IntersectionPoint(int point_id, int edge_id, const Point &pt) : IntersectionReference(point_id, edge_id), Point(pt) {}
IntersectionPoint(const IntersectionReference &ir, const Point &pt) : IntersectionReference(ir), Point(pt) {}
// Inherits coord_t x, y
};
class IntersectionLine : public Line
{
public: