Fixes and improvements to thin wall detection. #2829

This commit is contained in:
Alessandro Ranellucci 2015-05-22 01:46:01 +02:00
parent 70ec433e67
commit 31e0fc7f17
2 changed files with 59 additions and 47 deletions

View file

@ -191,12 +191,25 @@ ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines)
// unless they represent closed loops // unless they represent closed loops
for (Polylines::iterator polyline = polylines->begin(); polyline != polylines->end(); ++polyline) { for (Polylines::iterator polyline = polylines->begin(); polyline != polylines->end(); ++polyline) {
if (polyline->points.front().distance_to(polyline->points.back()) < min_width) continue; if (polyline->points.front().distance_to(polyline->points.back()) < min_width) continue;
// TODO: we should *not* extend endpoints where other polylines start/end
// (such as T joints, which are returned as three polylines by MedialAxis)
polyline->extend_start(max_width); polyline->extend_start(max_width);
polyline->extend_end(max_width); polyline->extend_end(max_width);
} }
// clip again after extending endpoints to prevent them from exceeding the expolygon boundaries // clip again after extending endpoints to prevent them from exceeding the expolygon boundaries
intersection(*polylines, *this, polylines); intersection(*polylines, *this, polylines);
// remove too short polylines
// (we can't do this check before endpoints extension and clipping because we don't
// know how long will the endpoints be extended since it depends on polygon thickness
// which is variable - extension will be <= max_width/2 on each side)
for (size_t i = 0; i < polylines->size(); ++i) {
if ((*polylines)[i].length() < max_width) {
polylines->erase(polylines->begin() + i);
--i;
}
}
} }
void void

View file

@ -325,8 +325,11 @@ MedialAxis::build(Polylines* polylines)
} }
*/ */
typedef const VD::vertex_type vert_t;
typedef const VD::edge_type edge_t;
// collect valid edges (i.e. prune those not belonging to MAT) // collect valid edges (i.e. prune those not belonging to MAT)
// note: this keeps twins, so it contains twice the number of the valid edges // note: this keeps twins, so it inserts twice the number of the valid edges
this->edges.clear(); this->edges.clear();
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
// if we only process segments representing closed loops, none if the // if we only process segments representing closed loops, none if the
@ -336,61 +339,64 @@ MedialAxis::build(Polylines* polylines)
} }
// count valid segments for each vertex // count valid segments for each vertex
std::map< const VD::vertex_type*,std::set<const VD::edge_type*> > vertex_edges; std::map< vert_t*,std::set<edge_t*> > vertex_edges; // collects edges connected for each vertex
std::set<const VD::vertex_type*> entry_nodes; std::set<vert_t*> startpoints; // collects all vertices having a single starting edge
for (VD::const_vertex_iterator vertex = this->vd.vertices().begin(); vertex != this->vd.vertices().end(); ++vertex) { for (VD::const_vertex_iterator it = this->vd.vertices().begin(); it != this->vd.vertices().end(); ++it) {
// get a reference to the list of valid edges originating from this vertex vert_t* vertex = &*it;
std::set<const VD::edge_type*>& edges = vertex_edges[&*vertex];
// get one random edge originating from this vertex // loop through all edges originating from this vertex
const VD::edge_type* edge = vertex->incident_edge(); // starting from a random one
edge_t* edge = vertex->incident_edge();
do { do {
if (this->edges.count(edge) > 0) // only count valid edges // if this edge was not pruned by our filter above,
edges.insert(edge); // add it to vertex_edges
edge = edge->rot_next(); // next edge originating from this vertex if (this->edges.count(edge) > 0)
vertex_edges[vertex].insert(edge);
// continue looping next edge originating from this vertex
edge = edge->rot_next();
} while (edge != vertex->incident_edge()); } while (edge != vertex->incident_edge());
// if there's only one edge starting at this vertex then it's a leaf // if there's only one edge starting at this vertex then it's an endpoint
size_t edge_count = edges.size(); if (vertex_edges[vertex].size() == 1) {
if (edge_count == 1) { startpoints.insert(vertex);
entry_nodes.insert(&*vertex);
} }
} }
// prune recursively // prune startpoints recursively if extreme segments are not valid
while (!entry_nodes.empty()) { while (!startpoints.empty()) {
// get a random entry node // get a random entry node
const VD::vertex_type* v = *entry_nodes.begin(); vert_t* v = *startpoints.begin();
// get edge starting from v // get edge starting from v
assert(!vertex_edges[v].empty()); assert(vertex_edges[v].size() == 1);
const VD::edge_type* edge = *vertex_edges[v].begin(); edge_t* edge = *vertex_edges[v].begin();
if (!this->is_valid_edge(*edge)) { if (!this->is_valid_edge(*edge)) {
// if edge is not valid, erase it from edge list // if edge is not valid, erase it and its twin from edge list
(void)this->edges.erase(edge); (void)this->edges.erase(edge);
(void)this->edges.erase(edge->twin()); (void)this->edges.erase(edge->twin());
// decrement edge counters for the affected nodes // decrement edge counters for the affected nodes
const VD::vertex_type* v1 = edge->vertex1(); vert_t* v1 = edge->vertex1();
(void)vertex_edges[v].erase(edge); (void)vertex_edges[v].erase(edge);
(void)vertex_edges[v1].erase(edge->twin()); (void)vertex_edges[v1].erase(edge->twin());
// also, check whether the end vertex is a new leaf // also, check whether the end vertex is a new leaf
if (vertex_edges[v1].size() == 1) { if (vertex_edges[v1].size() == 1) {
entry_nodes.insert(v1); startpoints.insert(v1);
} else if (vertex_edges[v1].empty()) { } else if (vertex_edges[v1].empty()) {
entry_nodes.erase(v1); startpoints.erase(v1);
} }
} }
// remove node from the set to prevent it from being visited again // remove node from the set to prevent it from being visited again
entry_nodes.erase(v); startpoints.erase(v);
} }
// iterate through the valid edges to build polylines // iterate through the valid edges to build polylines
while (!this->edges.empty()) { while (!this->edges.empty()) {
const VD::edge_type& edge = **this->edges.begin(); edge_t &edge = **this->edges.begin();
// start a polyline // start a polyline
Polyline polyline; Polyline polyline;
@ -405,13 +411,14 @@ MedialAxis::build(Polylines* polylines)
this->process_edge_neighbors(edge, &polyline.points); this->process_edge_neighbors(edge, &polyline.points);
// get previous points // get previous points
Points pp; {
this->process_edge_neighbors(*edge.twin(), &pp); Points pp;
polyline.points.insert(polyline.points.begin(), pp.rbegin(), pp.rend()); this->process_edge_neighbors(*edge.twin(), &pp);
polyline.points.insert(polyline.points.begin(), pp.rbegin(), pp.rend());
}
// append polyline to result if it's not too small // append polyline to result
if (polyline.length() > this->max_width) polylines->push_back(polyline);
polylines->push_back(polyline);
} }
} }
@ -458,12 +465,12 @@ MedialAxis::is_valid_edge(const VD::edge_type& edge) const
// calculate the relative angle between the two boundary segments // calculate the relative angle between the two boundary segments
double angle = fabs(segment2.orientation() - segment1.orientation()); double angle = fabs(segment2.orientation() - segment1.orientation());
if (angle > PI) angle -= PI;
// fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction) // fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction)
// we're interested only in segments close to the second case (facing segments) // we're interested only in segments close to the second case (facing segments)
// so we allow some tolerance (say, 30°) // so we allow some tolerance.
if (angle < PI*2/3) { // this filter ensures that we're dealing with a narrow/oriented area (longer than thick)
if (fabs(angle - PI) > PI/5) {
return false; return false;
} }
@ -474,11 +481,11 @@ MedialAxis::is_valid_edge(const VD::edge_type& edge) const
// our skeleton // our skeleton
// get perpendicular distance of each edge vertex to the segment(s) // get perpendicular distance of each edge vertex to the segment(s)
Line line = this->edge_to_line(edge); double dist0 = segment1.a.distance_to(segment2.b);
double dist0 = line.a.perp_distance_to(segment1); double dist1 = segment1.b.distance_to(segment2.a);
double dist1 = line.b.perp_distance_to(segment1);
/* /*
Line line = this->edge_to_line(edge);
double diff = fabs(dist1 - dist0); double diff = fabs(dist1 - dist0);
double dist_between_segments1 = segment1.a.distance_to(segment2); double dist_between_segments1 = segment1.a.distance_to(segment2);
double dist_between_segments2 = segment1.b.distance_to(segment2); double dist_between_segments2 = segment1.b.distance_to(segment2);
@ -493,18 +500,10 @@ MedialAxis::is_valid_edge(const VD::edge_type& edge) const
// if this edge is the centerline for a very thin area, we might want to skip it // if this edge is the centerline for a very thin area, we might want to skip it
// in case the area is too thin // in case the area is too thin
if (dist0 < this->min_width/2 && dist1 < this->min_width/2) { if (dist0 < this->min_width && dist1 < this->min_width) {
//printf(" => too thin, skipping\n"); //printf(" => too thin, skipping\n");
return false; return false;
} }
// if only one of the two edge vertices is the centerline of a very narrow area,
// it means the shape is shrinking on that size (dist0 or dist1 might be even 0
// when we have a narrow triangle), so in this case we only keep the edge if it's
// long enough, otherwise it's an artifact
if (dist0 < this->min_width/2 || dist1 < this->min_width/2) {
if (line.length() < this->max_width) return false;
}
return true; return true;
} }