Fixes and improvements to thin wall detection. #2829
This commit is contained in:
parent
70ec433e67
commit
31e0fc7f17
2 changed files with 59 additions and 47 deletions
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue