Issue 718 (#719)

* WIP: first part of fix - generation of hole cutlines

First problem was that hole cutlines got extended
over the whole length and sometimes lead to coincident
edges which are difficult to resolve for the polygon
cutter.

* Refined solution, fixed #718

- disabled elaborate hole insertion procedure for now as the
  performance impact has to be assessed yet and the new scheme
  will break many tests
- stricter rejection of ambiguous configurations in the polygon cutter
- fallback is boolean AND now since there is no need to re-invoke the
  polygon cutter (we can't do so as we made it more strict).
  Performance-wise we replace a merge by an AND step which may even be
  faster the output is smaller and the polygon cutter does not need
  to be re-invoked.

* Compatibility with other STLs
This commit is contained in:
Matthias Köfferlein 2021-01-31 19:21:15 +01:00 committed by GitHub
parent 10eee4d895
commit 4134829304
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 225 additions and 71 deletions

View File

@ -1060,7 +1060,15 @@ EdgeProcessor::insert (const db::Edge &e, EdgeProcessor::property_type p)
}
}
void
void
EdgeProcessor::insert (const db::SimplePolygon &q, EdgeProcessor::property_type p)
{
for (db::SimplePolygon::polygon_edge_iterator e = q.begin_edge (); ! e.at_end (); ++e) {
insert (*e, p);
}
}
void
EdgeProcessor::insert (const db::Polygon &q, EdgeProcessor::property_type p)
{
for (db::Polygon::polygon_edge_iterator e = q.begin_edge (); ! e.at_end (); ++e) {

View File

@ -633,6 +633,11 @@ public:
*/
void insert (const db::Polygon &q, property_type p = 0);
/**
* @brief Insert a simple polygon
*/
void insert (const db::SimplePolygon &q, property_type p = 0);
/**
* @brief Insert a polygon reference
*/

View File

@ -90,6 +90,8 @@ public:
void push_front (const db::Point &p) { m_contour.push_front (p); }
void pop_back () { m_contour.pop_back (); }
void pop_front () { m_contour.pop_front (); }
iterator erase (iterator i) { return m_contour.erase (i); }
iterator insert (iterator i, const db::Point &p) { return m_contour.insert (i, p); }
bool empty () const { return m_contour.empty (); }
size_t size () const { return m_contour.size (); }
@ -136,9 +138,12 @@ public:
}
template <class I>
void insert (iterator at, I from, I to)
iterator insert (iterator at, I from, I to)
{
// NOTE: in some STL m_contour.insert already returns the new iterator
size_t index_at = at - m_contour.begin ();
m_contour.insert (at, from, to);
return m_contour.begin () + index_at;
}
private:
@ -725,27 +730,56 @@ PolygonGenerator::join_contours (db::Coord x)
PGPolyContour &cprev = (*mp_contours) [iprev];
tl_assert (cprev.size () >= 2);
tl_assert (c1.size () >= 2);
PGPolyContour::iterator ins = cprev.end ();
db::Coord xprev = 0;
db::Edge eprev;
#if 1
// shallow analysis: insert the cutline at the end of the sequence - this may
// cut lines collinear with contour edges
eprev = db::Edge (ins[-2], ins[-1]);
xprev = db::coord_traits<db::Coord>::rounded (edge_xaty (db::Edge (ins[-2], ins[-1]), m_y));
#else
// deep analysis: determine insertion point: pick the one where the cutline is shortest
for (PGPolyContour::iterator i = ins; i > cprev.begin () + 1; --i) {
db::Edge ecut (i[-2], i[-1]);
db::Coord xcut = db::coord_traits<db::Coord>::rounded (edge_xaty (db::Edge (i[-2], i[-1]), m_y));
if (ins == i || (i[-1].y () >= m_y && i[-2].y () < m_y && xcut < c1.back ().x () && xcut > xprev)) {
xprev = xcut;
eprev = ecut;
ins = i;
}
}
#endif
// compute intersection point with next edge
db::Edge eprev (cprev.end ()[-2], cprev.back ());
db::Coord xprev = db::coord_traits<db::Coord>::rounded (edge_xaty (eprev, m_y));
db::Point pprev (xprev, m_y);
// remove collinear edges along the cut line
cprev.back () = pprev;
while (cprev.size () > 1 && cprev.end ()[-2].y () == m_y && cprev.end ()[-1].y () == m_y) {
cprev.pop_back ();
ins[-1] = pprev;
while (ins - cprev.begin () > 1 && ins[-2].y () == m_y && ins[-1].y () == m_y) {
ins = cprev.erase (ins - 1);
}
tl_assert (c1.size () >= 2);
if ((c1.begin () + 1)->y () == m_y) {
cprev.insert (cprev.end (), c1.begin () + 1, c1.end ());
ins = cprev.insert (ins, c1.begin () + 1, c1.end ());
ins += c1.size () - 1;
} else {
cprev.insert (cprev.end (), c1.begin (), c1.end ());
ins = cprev.insert (ins, c1.begin (), c1.end ());
ins += c1.size ();
}
cprev.push_back (pprev);
ins = cprev.insert (ins, pprev);
++ins;
if (eprev.p2 () != pprev) {
cprev.push_back (eprev.p2 ());
cprev.insert (ins, eprev.p2 ());
}
mp_contours->free (i1);

View File

@ -185,10 +185,19 @@ public:
return db::vprod_sign (edge (), other.edge ()) > 0;
}
}
bool operator== (const loose_end_struct<CuttingEdgeType> &other) const
{
if (! db::coord_traits<double>::equal (proj (), other.proj ())) {
return false;
} else {
return db::vprod_sign (edge (), other.edge ()) == 0;
}
}
};
template <class PolygonType, class Edge>
static bool _cut_polygon_internal (const PolygonType &input, const Edge &line, CutPolygonReceiverBase *right_of_line)
static bool _cut_polygon_internal (const PolygonType &input, const Edge &line, cut_polygon_receiver_base<PolygonType> *right_of_line)
{
typedef typename PolygonType::point_type point_type;
typedef typename PolygonType::coord_type coord_type;
@ -259,6 +268,7 @@ static bool _cut_polygon_internal (const PolygonType &input, const Edge &line, C
// tie together last and first partial segments.
if (cutting_segments[nfirst].segment < 0) {
cutting_segments[nfirst].enter = cutting_segments.back ().enter;
cutting_segments[nfirst].segment = cutting_segments.back ().segment;
cutting_segments.pop_back ();
}
@ -268,7 +278,7 @@ static bool _cut_polygon_internal (const PolygonType &input, const Edge &line, C
if (nc == 0) {
// the hull is fully on the right side -> just output the input polygon and that's it.
right_of_line->put (&input);
right_of_line->put (input);
return true;
} else {
// remember hole contours for later assignment
@ -279,7 +289,7 @@ static bool _cut_polygon_internal (const PolygonType &input, const Edge &line, C
} else {
PolygonType poly;
poly.assign_hull (contour.begin (), contour.end ());
right_of_line->put (&poly);
right_of_line->put (poly);
}
}
@ -297,23 +307,28 @@ static bool _cut_polygon_internal (const PolygonType &input, const Edge &line, C
std::stable_sort (loose_ends.begin (), loose_ends.end ());
// bring the points in a strict enter/leave order if possible
// we allow single pairs of collinear entry/leave edges (cut lines) and bring them in the right order
bool enter = false;
for (typename std::vector<loose_end_struct<cut_polygon_edge_type> >::iterator i = loose_ends.begin (); i != loose_ends.end (); ++i) {
if (i + 1 != loose_ends.end () && i[1] == i[0]) {
if (i + 2 != loose_ends.end () && i[2] == i[0]) {
// triple collinear
return false;
}
if (i[0].enter != enter && i[1].enter == enter) {
std::swap (i[0], i[1]);
}
}
enter = !enter;
}
// the points now have to be in strict enter/leave order - otherwise fallback to merge
enter = false;
for (typename std::vector<loose_end_struct<cut_polygon_edge_type> >::iterator i = loose_ends.begin (); i != loose_ends.end (); ++i) {
if (i->enter != enter) {
typename std::vector<loose_end_struct<cut_polygon_edge_type> >::iterator j = i + 1;
typename std::vector<loose_end_struct<cut_polygon_edge_type> >::iterator jj = loose_ends.end ();
for ( ; j != loose_ends.end () && !(*j < *i) && !(*i < *j); ++j) {
if (j->enter == enter) {
jj = j;
break;
}
}
if (jj == loose_ends.end ()) {
return false; // cannot cut (self-overlapping, self-intersecting)
}
std::swap (*jj, *i);
return false;
}
enter = !enter;
}
@ -410,7 +425,7 @@ static bool _cut_polygon_internal (const PolygonType &input, const Edge &line, C
// it might happen in some cases, that cut pieces may vanish (i.e. all points on a line). Thus we check, if that
// is the case and do not produce a polygon then.
if (poly.vertices () > 0) {
right_of_line->put (&poly);
right_of_line->put (poly);
}
}
@ -441,7 +456,7 @@ static bool _cut_polygon_internal (const PolygonType &input, const Edge &line, C
}
}
right_of_line->put (&*hull);
right_of_line->put (*hull);
}
@ -452,7 +467,7 @@ static bool _cut_polygon_internal (const PolygonType &input, const Edge &line, C
// we assign to a PolygonType, this check is not possible.
for (typename std::vector<PolygonType>::iterator hole = hole_polygons.begin (); hole != hole_polygons.end (); ++hole) {
if (hole->vertices () > 0) {
right_of_line->put (&*hole);
right_of_line->put (*hole);
}
}
@ -474,23 +489,22 @@ namespace
/**
* @brief A polygon sink for the edge processor that feeds the polygon into the cut algorithm
*/
template <class Sink, class PolygonType, class Edge>
struct cut_polygon_sink
template <class Sink, class PolygonType>
struct cut_polygon_bool_sink
: public Sink
{
cut_polygon_sink (const Edge &_line, CutPolygonReceiverBase *_right_of_line)
: line (_line), right_of_line (_right_of_line)
cut_polygon_bool_sink (cut_polygon_receiver_base<PolygonType> *_right_of_line)
: right_of_line (_right_of_line)
{
// .. nothing yet ..
}
virtual void put (const PolygonType &poly)
{
tl_assert (_cut_polygon_internal (poly, line, right_of_line));
right_of_line->put (poly);
}
Edge line;
CutPolygonReceiverBase *right_of_line;
cut_polygon_receiver_base<PolygonType> *right_of_line;
};
/**
@ -500,20 +514,31 @@ namespace
* fallback.
*/
template <class PolygonType, class Edge>
void cut_polygon_internal_int (const PolygonType &input, const Edge &line, CutPolygonReceiverBase *right_of_line)
void cut_polygon_internal_int (const PolygonType &input, const Edge &line, cut_polygon_receiver_base<PolygonType> *right_of_line)
{
bool ok = _cut_polygon_internal (input, line, right_of_line);
if (! ok) {
// If the cut operation fails on the plain input, merge the input polygon and try again
// If the fast cut operation fails, use boolean AND to perform the cut operation
db::EdgeProcessor ep;
ep.insert_sequence (input.begin_edge ());
db::SimpleMerge op;
PolygonType clip (input.box ());
std::vector<PolygonType> mask;
cut_polygon (clip, line, std::back_inserter (mask));
cut_polygon_sink<typename get_sink_type<PolygonType>::result, PolygonType, Edge> sink (line, right_of_line);
db::PolygonGenerator pg (sink);
ep.process (pg, op);
if (! mask.empty ()) {
db::EdgeProcessor ep;
ep.insert_sequence (input.begin_edge (), 0);
ep.insert_sequence (mask.begin (), mask.end (), 1);
db::BooleanOp op (BooleanOp::And);
cut_polygon_bool_sink<typename get_sink_type<PolygonType>::result, PolygonType> sink (right_of_line);
db::PolygonGenerator pg (sink);
ep.process (pg, op);
}
}
@ -524,14 +549,14 @@ namespace
*/
template <class PolygonType, class IPolygonType>
class cut_polygon_receiver_double_impl
: public CutPolygonReceiverBase
: public cut_polygon_receiver_base<IPolygonType>
{
public:
cut_polygon_receiver_double_impl ()
: mp_next (0)
{ }
void set_next (CutPolygonReceiverBase *next)
void set_next (cut_polygon_receiver_base<PolygonType> *next)
{
mp_next = next;
}
@ -541,14 +566,14 @@ namespace
m_tr = tr;
}
virtual void put (const void *p)
virtual void put (const IPolygonType &p)
{
PolygonType pp = ((const IPolygonType *) p)->transformed (m_tr, false);
mp_next->put ((void *) &pp);
PolygonType pp = p.transformed (m_tr, false);
mp_next->put (pp);
}
private:
CutPolygonReceiverBase *mp_next;
cut_polygon_receiver_base<PolygonType> *mp_next;
db::CplxTrans m_tr;
};
@ -565,7 +590,7 @@ namespace
* transform the polygon to int. On output, the polygon is transformed back to double.
*/
template <class PolygonType, class Edge>
void cut_polygon_internal_double (const PolygonType &input, const Edge &line, CutPolygonReceiverBase *right_of_line)
void cut_polygon_internal_double (const PolygonType &input, const Edge &line, cut_polygon_receiver_base<PolygonType> *right_of_line)
{
db::DBox bbox = input.box ();
bbox += db::DBox (0, 0, 0, 0);
@ -585,22 +610,22 @@ namespace
}
template<> DB_PUBLIC void cut_polygon_internal (const db::Polygon &polygon, const db::Polygon::edge_type &line, CutPolygonReceiverBase *right_of_line)
template<> DB_PUBLIC void cut_polygon_internal (const db::Polygon &polygon, const db::Polygon::edge_type &line, cut_polygon_receiver_base<db::Polygon> *right_of_line)
{
cut_polygon_internal_int (polygon, line, right_of_line);
}
template<> DB_PUBLIC void cut_polygon_internal (const db::SimplePolygon &polygon, const db::SimplePolygon::edge_type &line, CutPolygonReceiverBase *right_of_line)
template<> DB_PUBLIC void cut_polygon_internal (const db::SimplePolygon &polygon, const db::SimplePolygon::edge_type &line, cut_polygon_receiver_base<db::SimplePolygon> *right_of_line)
{
cut_polygon_internal_int (polygon, line, right_of_line);
}
template<> DB_PUBLIC void cut_polygon_internal (const db::DPolygon &polygon, const db::DPolygon::edge_type &line, CutPolygonReceiverBase *right_of_line)
template<> DB_PUBLIC void cut_polygon_internal (const db::DPolygon &polygon, const db::DPolygon::edge_type &line, cut_polygon_receiver_base<db::DPolygon> *right_of_line)
{
cut_polygon_internal_double (polygon, line, right_of_line);
}
template<> DB_PUBLIC void cut_polygon_internal (const db::DSimplePolygon &polygon, const db::DSimplePolygon::edge_type &line, CutPolygonReceiverBase *right_of_line)
template<> DB_PUBLIC void cut_polygon_internal (const db::DSimplePolygon &polygon, const db::DSimplePolygon::edge_type &line, cut_polygon_receiver_base<db::DSimplePolygon> *right_of_line)
{
cut_polygon_internal_double (polygon, line, right_of_line);
}

View File

@ -74,25 +74,26 @@ private:
// Some helper classes and functions for implementing cut_polygon
class DB_PUBLIC CutPolygonReceiverBase
template <class Polygon>
class DB_PUBLIC cut_polygon_receiver_base
{
public:
virtual ~CutPolygonReceiverBase () { }
virtual void put (const void *) = 0;
virtual ~cut_polygon_receiver_base () { }
virtual void put (const Polygon &) = 0;
};
template <class OutputIter, class Polygon>
class cut_polygon_receiver
: public CutPolygonReceiverBase
: public cut_polygon_receiver_base<Polygon>
{
public:
cut_polygon_receiver (const OutputIter &iter)
: m_iter (iter)
{ }
virtual void put (const void *polygon)
virtual void put (const Polygon &polygon)
{
*m_iter++ = *((const Polygon *) polygon);
*m_iter++ = polygon;
}
private:
@ -100,7 +101,7 @@ private:
};
template <class PolygonType, class Edge>
void DB_PUBLIC cut_polygon_internal (const PolygonType &input, const Edge &line, CutPolygonReceiverBase *right_of_line);
void DB_PUBLIC cut_polygon_internal (const PolygonType &input, const Edge &line, cut_polygon_receiver_base<PolygonType> *right_of_line);
/**
* @brief Polygon cut function

View File

@ -2570,6 +2570,67 @@ TEST(102)
EXPECT_EQ (out[0].to_string (), "(0,0;0,200;100,200;100,100;200,100;200,200;500,200;500,100;600,100;600,200;0,200;0,1000;1000,1000;1000,0)");
}
TEST(103)
{
db::EdgeProcessor ep;
{
db::Point pts[] = {
db::Point (0, 0),
db::Point (0, 500),
db::Point (1500, 500),
db::Point (1500, 0),
db::Point (1000, 0),
db::Point (1000, 400),
db::Point (500, 400),
db::Point (500, 0)
};
db::Polygon p;
p.assign_hull (&pts[0], &pts[sizeof(pts) / sizeof(pts[0])]);
ep.insert (p, 0);
}
{
db::Point pts[] = {
db::Point (100, 100),
db::Point (100, 400),
db::Point (400, 400),
db::Point (400, 100)
};
db::Polygon p;
p.assign_hull (&pts[0], &pts[sizeof(pts) / sizeof(pts[0])]);
ep.insert (p, 1);
}
{
db::Point pts[] = {
db::Point (1100, 100),
db::Point (1100, 400),
db::Point (1400, 400),
db::Point (1400, 100)
};
db::Polygon p;
p.assign_hull (&pts[0], &pts[sizeof(pts) / sizeof(pts[0])]);
ep.insert (p, 1);
}
std::vector<db::Polygon> out;
db::PolygonContainer pc (out);
db::PolygonGenerator pg (pc, true, true);
db::BooleanOp op (db::BooleanOp::ANotB);
ep.process (pg, op);
EXPECT_EQ (out.size (), size_t (1));
#if 1
// fast hole treatment
EXPECT_EQ (out[0].to_string (), "(0,0;0,400;100,400;100,100;400,100;400,400;1100,400;1100,100;1400,100;1400,400;0,400;0,500;1500,500;1500,0;1000,0;1000,400;500,400;500,0)");
#else
// elaborate hole treatment
EXPECT_EQ (out[0].to_string (), "(0,0;0,400;100,400;100,100;400,100;400,400;0,400;0,500;1500,500;1500,0;1000,0;1000,400;1100,400;1100,100;1400,100;1400,400;500,400;500,0)");
#endif
}
// Bug 134
TEST(134)
{

View File

@ -559,15 +559,18 @@ TEST(9c)
std::vector<db::Polygon> right_of;
db::cut_polygon (in, db::Edge (db::Point (15835, 0), db::Point (15835, 1)), std::back_inserter (right_of));
EXPECT_EQ (right_of.size (), size_t (3));
EXPECT_EQ (right_of[0].to_string (), "(17335,8265;16335,9265;15835,9265;15835,9765;17335,9765)");
EXPECT_EQ (right_of[1].to_string (), "(15835,9765;16002,9932;15835,10015;17335,10015;17335,9765)");
EXPECT_EQ (right_of[2].to_string (), "(15835,10015;15835,10265;17335,10265;17335,10015)");
EXPECT_EQ (right_of.size (), size_t (1));
EXPECT_EQ (right_of[0].to_string (), "(17335,8265;16335,9265;15835,9265;15835,9765;16002,9932;15835,10015;15835,10265;17335,10265)");
right_of.clear ();
db::cut_polygon (in, db::Edge (db::Point (15835, 1), db::Point (15835, 0)), std::back_inserter (right_of));
EXPECT_EQ (right_of.size (), size_t (1));
EXPECT_EQ (right_of[0].to_string (), "(14335,8265;14335,10265;15335,10265;15335,9765;15668,9932;15335,10265;15835,10265;15835,10015;15668,9932;15835,9765;15335,9765;14335,9265;15335,9265;15335,9765;15835,9765;15835,9265;15335,9265)");
EXPECT_EQ (right_of.size (), size_t (4));
if (right_of.size () >= 4) {
EXPECT_EQ (right_of[0].to_string (), "(14335,8265;14335,9265;15335,9265)");
EXPECT_EQ (right_of[1].to_string (), "(15335,9265;15335,9765;15668,9932;15835,9765;15835,9265)");
EXPECT_EQ (right_of[2].to_string (), "(14335,9265;14335,10265;15335,10265;15335,9765)");
EXPECT_EQ (right_of[3].to_string (), "(15668,9932;15335,10265;15835,10265;15835,10015)");
}
}
TEST(9d)
@ -2373,6 +2376,23 @@ TEST(404)
}
}
TEST(405)
{
db::Polygon poly;
std::string s ("(0,0;0,1126;30,1126;30,30;3044,30;3044,1126;5782,1126;5782,30;8796,30;8796,1126;0,1126;0,1141;3009,1141;3009,1156;3194,1156;3194,1141;8826,1141;8826,0;5742,0;5742,1126;3084,1126;3084,0)");
tl::Extractor ex (s.c_str ());
ex.read (poly);
std::vector<db::Polygon> sp;
db::split_polygon (poly, sp);
EXPECT_EQ (sp.size (), size_t (2));
if (sp.size () >= 2) {
EXPECT_EQ (sp[0].to_string (), "(5742,0;5742,1126;5782,1126;5782,30;8796,30;8796,1126;3194,1126;3194,1141;8826,1141;8826,0)");
EXPECT_EQ (sp[1].to_string (), "(0,0;0,1126;30,1126;30,30;3044,30;3044,1126;0,1126;0,1141;3009,1141;3009,1156;3194,1156;3194,1126;3084,1126;3084,0)");
}
}
static db::Polygon str2poly (const std::string &s)
{
db::Polygon poly;
@ -2382,7 +2402,7 @@ static db::Polygon str2poly (const std::string &s)
}
// self-overlapping, non-orientable check
TEST(405)
TEST(500)
{
std::string ps;
std::vector<db::Polygon> parts;

Binary file not shown.