diff --git a/src/db/db/db.pro b/src/db/db/db.pro index bbae9671a..732d42aa1 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -76,6 +76,7 @@ SOURCES = \ dbPCellVariant.cc \ dbPoint.cc \ dbPolygon.cc \ + dbPolygonGraph.cc \ dbPolygonNeighborhood.cc \ dbPolygonTools.cc \ dbPolygonGenerators.cc \ @@ -314,6 +315,7 @@ HEADERS = \ dbPCellVariant.h \ dbPoint.h \ dbPolygon.h \ + dbPolygonGraph.h \ dbPolygonNeighborhood.h \ dbPolygonTools.h \ dbPolygonGenerators.h \ diff --git a/src/db/db/dbPolygonGraph.cc b/src/db/db/dbPolygonGraph.cc new file mode 100644 index 000000000..c7ae4a349 --- /dev/null +++ b/src/db/db/dbPolygonGraph.cc @@ -0,0 +1,846 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#include "dbPolygonGraph.h" +#include "dbLayout.h" +#include "dbWriter.h" +#include "tlStream.h" +#include "tlLog.h" +#include "tlTimer.h" + +#include +#include +#include +#include + +namespace db +{ + +// ------------------------------------------------------------------------------------- +// GVertex implementation + +GVertex::GVertex () + : DPoint (), m_is_precious (false) +{ + // .. nothing yet .. +} + +GVertex::GVertex (const db::DPoint &p) + : DPoint (p), m_is_precious (false) +{ + // .. nothing yet .. +} + +GVertex::GVertex (const GVertex &v) + : DPoint (), m_is_precious (false) +{ + operator= (v); +} + +GVertex &GVertex::operator= (const GVertex &v) +{ + if (this != &v) { + // NOTE: edges are not copied! + db::DPoint::operator= (v); + m_is_precious = v.m_is_precious; + } + return *this; +} + +GVertex::GVertex (db::DCoord x, db::DCoord y) + : DPoint (x, y), m_is_precious (false) +{ + // .. nothing yet .. +} + +#if 0 // @@@ +bool +GVertex::is_outside () const +{ + for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) { + if ((*e)->is_outside ()) { + return true; + } + } + return false; +} +#endif + +std::vector +GVertex::polygons () const +{ + std::set seen; + std::vector res; + for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) { + for (auto t = (*e)->begin_polygons (); t != (*e)->end_polygons (); ++t) { + if (seen.insert (t.operator-> ()).second) { + res.push_back (t.operator-> ()); + } + } + } + return res; +} + +bool +GVertex::has_edge (const GPolygonEdge *edge) const +{ + for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) { + if (*e == edge) { + return true; + } + } + return false; +} + +size_t +GVertex::num_edges (int max_count) const +{ + if (max_count < 0) { + // NOTE: this can be slow for a std::list, so we have max_count to limit this effort + return mp_edges.size (); + } else { + size_t n = 0; + for (auto i = mp_edges.begin (); i != mp_edges.end () && --max_count >= 0; ++i) { + ++n; + } + return n; + } +} + +std::string +GVertex::to_string (bool with_id) const +{ + std::string res = tl::sprintf ("(%.12g, %.12g)", x (), y()); + if (with_id) { + res += tl::sprintf ("[%x]", (size_t)this); + } + return res; +} + +int +GVertex::in_circle (const DPoint &point, const DPoint ¢er, double radius) +{ + double dx = point.x () - center.x (); + double dy = point.y () - center.y (); + double d2 = dx * dx + dy * dy; + double r2 = radius * radius; + double delta = fabs (d2 + r2) * db::epsilon; + if (d2 < r2 - delta) { + return 1; + } else if (d2 < r2 + delta) { + return 0; + } else { + return -1; + } +} + +// ------------------------------------------------------------------------------------- +// GPolygonEdge implementation + +GPolygonEdge::GPolygonEdge () + : mp_v1 (0), mp_v2 (0), mp_left (), mp_right (), m_level (0), m_id (0), m_is_segment (false) +{ + // .. nothing yet .. +} + +GPolygonEdge::GPolygonEdge (GVertex *v1, GVertex *v2) + : mp_v1 (v1), mp_v2 (v2), mp_left (), mp_right (), m_level (0), m_id (0), m_is_segment (false) +{ + // .. nothing yet .. +} + +void +GPolygonEdge::set_left (GPolygon *t) +{ + mp_left = t; +} + +void +GPolygonEdge::set_right (GPolygon *t) +{ + mp_right = t; +} + +void +GPolygonEdge::link () +{ + mp_v1->mp_edges.push_back (this); + m_ec_v1 = --mp_v1->mp_edges.end (); + + mp_v2->mp_edges.push_back (this); + m_ec_v2 = --mp_v2->mp_edges.end (); +} + +void +GPolygonEdge::unlink () +{ + if (mp_v1) { + mp_v1->remove_edge (m_ec_v1); + } + if (mp_v2) { + mp_v2->remove_edge (m_ec_v2); + } + mp_v1 = mp_v2 = 0; +} + +GPolygon * +GPolygonEdge::other (const GPolygon *t) const +{ + if (t == mp_left) { + return mp_right; + } + if (t == mp_right) { + return mp_left; + } + tl_assert (false); + return 0; +} + +GVertex * +GPolygonEdge::other (const GVertex *t) const +{ + if (t == mp_v1) { + return mp_v2; + } + if (t == mp_v2) { + return mp_v1; + } + tl_assert (false); + return 0; +} + +bool +GPolygonEdge::has_vertex (const GVertex *v) const +{ + return mp_v1 == v || mp_v2 == v; +} + +GVertex * +GPolygonEdge::common_vertex (const GPolygonEdge *other) const +{ + if (has_vertex (other->v1 ())) { + return (other->v1 ()); + } + if (has_vertex (other->v2 ())) { + return (other->v2 ()); + } + return 0; +} + +std::string +GPolygonEdge::to_string (bool with_id) const +{ + std::string res = std::string ("(") + mp_v1->to_string (with_id) + ", " + mp_v2->to_string (with_id) + ")"; + if (with_id) { + res += tl::sprintf ("[%x]", (size_t)this); + } + return res; +} + +double +GPolygonEdge::distance (const db::DEdge &e, const db::DPoint &p) +{ + double l = db::sprod (p - e.p1 (), e.d ()) / e.d ().sq_length (); + db::DPoint pp; + if (l <= 0.0) { + pp = e.p1 (); + } else if (l >= 1.0) { + pp = e.p2 (); + } else { + pp = e.p1 () + e.d () * l; + } + return (p - pp).length (); +} + +bool +GPolygonEdge::crosses (const db::DEdge &e, const db::DEdge &other) +{ + return e.side_of (other.p1 ()) * e.side_of (other.p2 ()) < 0 && + other.side_of (e.p1 ()) * other.side_of (e.p2 ()) < 0; +} + +bool +GPolygonEdge::crosses_including (const db::DEdge &e, const db::DEdge &other) +{ + return e.side_of (other.p1 ()) * e.side_of (other.p2 ()) <= 0 && + other.side_of (e.p1 ()) * other.side_of (e.p2 ()) <= 0; +} + +db::DPoint +GPolygonEdge::intersection_point (const db::DEdge &e, const db::DEdge &other) +{ + return e.intersect_point (other).second; +} + +bool +GPolygonEdge::point_on (const db::DEdge &edge, const db::DPoint &point) +{ + if (edge.side_of (point) != 0) { + return false; + } else { + return db::sprod_sign (point - edge.p1 (), edge.d ()) * db::sprod_sign(point - edge.p2 (), edge.d ()) < 0; + } +} + +#if 0 // @@@ +bool +GPolygonEdge::is_for_outside_polygons () const +{ + return (left () && left ()->is_outside ()) || (right () && right ()->is_outside ()); +} +#endif + +bool +GPolygonEdge::has_polygon (const GPolygon *t) const +{ + return t != 0 && (left () == t || right () == t); +} + +// ------------------------------------------------------------------------------------- +// GPolygon implementation + +GPolygon::GPolygon () + : m_id (0) +{ + // .. nothing yet .. +} + +void +GPolygon::init () +{ + m_id = 0; + + if (mp_e.empty ()) { + return; + } + + std::vector e; + e.swap (mp_e); + + std::multimap v2e; + + for (auto i = e.begin (); i != e.end (); ++i) { + if (i != e.begin ()) { + v2e.insert (std::make_pair ((*i)->v1 (), *i)); + v2e.insert (std::make_pair ((*i)->v2 (), *i)); + } + } + + mp_e.reserve (e.size ()); + mp_e.push_back (e.front ()); + // NOTE: we assume the edges follow the clockwise orientation + mp_e.back ()->set_right (this); + + mp_v.reserve (e.size ()); + mp_v.push_back (mp_e.back ()->v1 ()); + + auto v = mp_e.back ()->v2 (); + + // join the edges in the order of the polygon + while (! v2e.empty ()) { + + mp_v.push_back (v); + + auto i = v2e.find (v); + tl_assert (i != v2e.end () && i->first == v && i->second != mp_e.back ()); + v2e.erase (i); + mp_e.push_back (i->second); + // NOTE: we assume the edges follow the clockwise orientation + mp_e.back ()->set_right (this); + + v = i->second->other (v); + i = v2e.find (v); + while (i != v2e.end () && i->first == v) { + if (i->second == mp_e.back ()) { + v2e.erase (i); + break; + } + ++i; + } + + } +} + +GPolygon::~GPolygon () +{ + unlink (); +} + +void +GPolygon::unlink () +{ + for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { + if ((*e)->left () == this) { + (*e)->set_left (0); + } + if ((*e)->right () == this) { + (*e)->set_right (0); + } + } +} + +std::string +GPolygon::to_string (bool with_id) const +{ + std::string res = "("; + for (int i = 0; i < int (size ()); ++i) { + if (i > 0) { + res += ", "; + } + if (vertex (i)) { + res += vertex (i)->to_string (with_id); + } else { + res += "(null)"; + } + } + res += ")"; + return res; +} + +double +GPolygon::area () const +{ + return fabs (db::vprod (mp_e[0]->d (), mp_e[1]->d ())) * 0.5; +} + +db::DBox +GPolygon::bbox () const +{ + db::DBox box; + for (auto i = mp_v.begin (); i != mp_v.end (); ++i) { + box += **i; + } + return box; +} + +GPolygonEdge * +GPolygon::find_edge_with (const GVertex *v1, const GVertex *v2) const +{ + for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { + if ((*e)->has_vertex (v1) && (*e)->has_vertex (v2)) { + return *e; + } + } + tl_assert (false); +} + +GPolygonEdge * +GPolygon::common_edge (const GPolygon *other) const +{ + for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { + if ((*e)->other (this) == other) { + return *e; + } + } + return 0; +} + +#if 0 // @@@ +int +GPolygon::contains (const db::DPoint &point) const +{ + auto c = *mp_v[2] - *mp_v[0]; + auto b = *mp_v[1] - *mp_v[0]; + + int vps = db::vprod_sign (c, b); + if (vps == 0) { + return db::vprod_sign (point - *mp_v[0], b) == 0 && db::vprod_sign (point - *mp_v[0], c) == 0 ? 0 : -1; + } + + int res = 1; + + const GVertex *vl = mp_v[2]; + for (int i = 0; i < 3; ++i) { + const GVertex *v = mp_v[i]; + int n = db::vprod_sign (point - *vl, *v - *vl) * vps; + if (n < 0) { + return -1; + } else if (n == 0) { + res = 0; + } + vl = v; + } + + return res; +} +#endif + +double +GPolygon::min_edge_length () const +{ + double lmin = mp_e[0]->d ().length (); + for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { + lmin = std::min (lmin, (*e)->d ().length ()); + } + return lmin; +} + +#if 0 // @@@ +double +GPolygon::b () const +{ + double lmin = min_edge_length (); + bool ok = false; + auto cr = circumcircle (&ok); + return ok ? lmin / cr.second : 0.0; +} +#endif + +bool +GPolygon::has_segment () const +{ + for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { + if ((*e)->is_segment ()) { + return true; + } + } + return false; +} + +unsigned int +GPolygon::num_segments () const +{ + unsigned int n = 0; + for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { + if ((*e)->is_segment ()) { + ++n; + } + } + return n; +} + +// ----------------------------------------------------------------------------------- + +static inline bool is_equal (const db::DPoint &a, const db::DPoint &b) +{ + return std::abs (a.x () - b.x ()) < std::max (1.0, (std::abs (a.x ()) + std::abs (b.x ()))) * db::epsilon && + std::abs (a.y () - b.y ()) < std::max (1.0, (std::abs (a.y ()) + std::abs (b.y ()))) * db::epsilon; +} + +PolygonGraph::PolygonGraph () + : m_id (0) + // @@@: m_is_constrained (false), m_level (0), m_id (0), m_flips (0), m_hops (0) +{ + // .. nothing yet .. +} + +PolygonGraph::~PolygonGraph () +{ + clear (); +} + +db::GVertex * +PolygonGraph::create_vertex (double x, double y) +{ + m_vertex_heap.push_back (db::GVertex (x, y)); + return &m_vertex_heap.back (); +} + +db::GVertex * +PolygonGraph::create_vertex (const db::DPoint &pt) +{ + m_vertex_heap.push_back (pt); + return &m_vertex_heap.back (); +} + +db::GPolygonEdge * +PolygonGraph::create_edge (db::GVertex *v1, db::GVertex *v2) +{ + db::GPolygonEdge *edge = 0; + + if (! m_returned_edges.empty ()) { + edge = m_returned_edges.back (); + m_returned_edges.pop_back (); + *edge = db::GPolygonEdge (v1, v2); + } else { + m_edges_heap.push_back (db::GPolygonEdge (v1, v2)); + edge = &m_edges_heap.back (); + } + + edge->link (); + edge->set_id (++m_id); + return edge; +} + +void +PolygonGraph::remove_polygon (db::GPolygon *poly) +{ + std::vector edges; + edges.reserve (poly->size ()); + for (int i = 0; i < int (poly->size ()); ++i) { + edges [i] = poly->edge (i); + } + + delete poly; + + // clean up edges we do no longer need + for (auto e = edges.begin (); e != edges.end (); ++e) { + if ((*e) && (*e)->left () == 0 && (*e)->right () == 0 && (*e)->v1 ()) { + (*e)->unlink (); + m_returned_edges.push_back (*e); + } + } +} + +void +PolygonGraph::insert_polygon (const db::DPolygon &polygon) +{ + if (polygon.begin_edge ().at_end ()) { + return; + } + + std::vector edges; + + for (unsigned int c = 0; c < polygon.holes () + 1; ++c) { + const db::DPolygon::contour_type &ctr = polygon.contour (c); + db::GVertex *v0 = 0, *vv, *v; + for (auto p = ctr.begin (); p != ctr.end (); ++p) { + v = create_vertex ((*p).x (), (*p).y ()); + if (! v0) { + v0 = v; + } else { + edges.push_back (create_edge (vv, v)); + } + vv = v; + } + if (v0 && v0 != v) { + edges.push_back (create_edge (v, v0)); + } + } + + create_polygon (edges.begin (), edges.end ()); +} + +std::string +PolygonGraph::to_string () +{ + std::string res; + for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) { + if (! res.empty ()) { + res += ", "; + } + res += t->to_string (); + } + return res; +} + +db::DBox +PolygonGraph::bbox () const +{ + db::DBox box; + for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) { + box += t->bbox (); + } + return box; +} + +#if 0 // @@@ +bool +PolygonGraph::check (bool check_delaunay) const +{ + bool res = true; + + if (check_delaunay) { + for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) { + auto cp = t->circumcircle (); + auto vi = find_inside_circle (cp.first, cp.second); + if (! vi.empty ()) { + res = false; + tl::error << "(check error) polygon does not meet Delaunay criterion: " << t->to_string (); + for (auto v = vi.begin (); v != vi.end (); ++v) { + tl::error << " vertex inside circumcircle: " << (*v)->to_string (true); + } + } + } + } + + for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) { + for (int i = 0; i < 3; ++i) { + if (! t->edge (i)->has_polygon (t.operator-> ())) { + tl::error << "(check error) edges " << t->edge (i)->to_string (true) + << " attached to polygon " << t->to_string (true) << " does not refer to this polygon"; + res = false; + } + } + } + + for (auto e = m_edges_heap.begin (); e != m_edges_heap.end (); ++e) { + + if (!e->left () && !e->right ()) { + continue; + } + + if (e->left () && e->right ()) { + if (e->left ()->is_outside () != e->right ()->is_outside () && ! e->is_segment ()) { + tl::error << "(check error) edge " << e->to_string (true) << " splits an outside and inside polygon, but is not a segment"; + res = false; + } + } + + for (auto t = e->begin_polygons (); t != e->end_polygons (); ++t) { + if (! t->has_edge (e.operator-> ())) { + tl::error << "(check error) edge " << e->to_string (true) << " not found in adjacent polygon " << t->to_string (true); + res = false; + } + if (! t->has_vertex (e->v1 ())) { + tl::error << "(check error) edges " << e->to_string (true) << " vertex 1 not found in adjacent polygon " << t->to_string (true); + res = false; + } + if (! t->has_vertex (e->v2 ())) { + tl::error << "(check error) edges " << e->to_string (true) << " vertex 2 not found in adjacent polygon " << t->to_string (true); + res = false; + } + db::GVertex *vopp = t->opposite (e.operator-> ()); + double sgn = (e->left () == t.operator-> ()) ? 1.0 : -1.0; + double vp = db::vprod (e->d(), *vopp - *e->v1 ()); // positive if on left side + if (vp * sgn <= 0.0) { + const char * side_str = sgn > 0.0 ? "left" : "right"; + tl::error << "(check error) external point " << vopp->to_string (true) << " not on " << side_str << " side of edge " << e->to_string (true); + res = false; + } + } + + if (! e->v1 ()->has_edge (e.operator-> ())) { + tl::error << "(check error) edge " << e->to_string (true) << " vertex 1 does not list this edge"; + res = false; + } + if (! e->v2 ()->has_edge (e.operator-> ())) { + tl::error << "(check error) edge " << e->to_string (true) << " vertex 2 does not list this edge"; + res = false; + } + + } + + for (auto v = m_vertex_heap.begin (); v != m_vertex_heap.end (); ++v) { + unsigned int num_outside_edges = 0; + for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { + if ((*e)->is_outside ()) { + ++num_outside_edges; + } + } + if (num_outside_edges > 0 && num_outside_edges != 2) { + tl::error << "(check error) vertex " << v->to_string (true) << " has " << num_outside_edges << " outside edges (can only be 2)"; + res = false; + for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { + if ((*e)->is_outside ()) { + tl::error << " Outside edge is " << (*e)->to_string (true); + } + } + } + } + + return res; +} +#endif + +db::Layout * +PolygonGraph::to_layout (bool decompose_by_id) const +{ + db::Layout *layout = new db::Layout (); + layout->dbu (0.001); + + auto dbu_trans = db::CplxTrans (layout->dbu ()).inverted (); + + db::Cell &top = layout->cell (layout->add_cell ("DUMP")); + unsigned int l1 = layout->insert_layer (db::LayerProperties (1, 0)); + // @@@ unsigned int l2 = layout->insert_layer (db::LayerProperties (2, 0)); + unsigned int l10 = layout->insert_layer (db::LayerProperties (10, 0)); + unsigned int l20 = layout->insert_layer (db::LayerProperties (20, 0)); + unsigned int l21 = layout->insert_layer (db::LayerProperties (21, 0)); + unsigned int l22 = layout->insert_layer (db::LayerProperties (22, 0)); + + std::vector pts; + for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) { + pts.clear (); + for (int i = 0; i < int (t->size ()); ++i) { + pts.push_back (*t->vertex (i)); + } + db::DPolygon poly; + poly.assign_hull (pts.begin (), pts.end ()); + top.shapes (/*@@@t->is_outside () ? l2 :*/ l1).insert (dbu_trans * poly); + if (decompose_by_id) { + if ((t->id () & 1) != 0) { + top.shapes (l20).insert (dbu_trans * poly); + } + if ((t->id () & 2) != 0) { + top.shapes (l21).insert (dbu_trans * poly); + } + if ((t->id () & 4) != 0) { + top.shapes (l22).insert (dbu_trans * poly); + } + } + } + + for (auto e = m_edges_heap.begin (); e != m_edges_heap.end (); ++e) { + if ((e->left () || e->right ()) && e->is_segment ()) { + top.shapes (l10).insert (dbu_trans * e->edge ()); + } + } + + return layout; +} + +void +PolygonGraph::dump (const std::string &path, bool decompose_by_id) const +{ + std::unique_ptr ly (to_layout (decompose_by_id)); + + tl::OutputStream stream (path); + + db::SaveLayoutOptions opt; + db::Writer writer (opt); + writer.write (*ly, stream); + + tl::info << "PolygonGraph written to " << path; +} + +void +PolygonGraph::clear () +{ + mp_polygons.clear (); + m_edges_heap.clear (); + m_vertex_heap.clear (); + m_returned_edges.clear (); + // @@@m_is_constrained = false; + // @@@m_level = 0; + m_id = 0; +} + +template +void +PolygonGraph::make_contours (const Poly &poly, const Trans &trans, std::vector > &edge_contours) +{ + edge_contours.push_back (std::vector ()); + for (auto pt = poly.begin_hull (); pt != poly.end_hull (); ++pt) { + edge_contours.back ().push_back (insert_point (trans * *pt)); + } + + for (unsigned int h = 0; h < poly.holes (); ++h) { + edge_contours.push_back (std::vector ()); + for (auto pt = poly.begin_hole (h); pt != poly.end_hole (h); ++pt) { + edge_contours.back ().push_back (insert_point (trans * *pt)); + } + } +} + +} diff --git a/src/db/db/dbPolygonGraph.h b/src/db/db/dbPolygonGraph.h new file mode 100644 index 000000000..cfbdee450 --- /dev/null +++ b/src/db/db/dbPolygonGraph.h @@ -0,0 +1,777 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef HDR_dbPolygonGraph +#define HDR_dbPolygonGraph + +#include "dbCommon.h" +#include "dbTriangle.h" +#include "dbBox.h" +#include "dbRegion.h" + +#include "tlObjectCollection.h" +#include "tlStableVector.h" + +#include +#include +#include +#include + +namespace db +{ + +class Layout; + +class GPolygon; +class GPolygonEdge; + +/** + * @brief A class representing a vertex in a Delaunay triangulation graph + * + * The vertex carries information about the connected edges and + * an integer value that can be used in traversal algorithms + * ("level") + */ +class DB_PUBLIC GVertex + : public db::DPoint +{ +public: + typedef std::list edges_type; + typedef edges_type::const_iterator edges_iterator; + typedef edges_type::iterator edges_iterator_non_const; + + GVertex (); + GVertex (const DPoint &p); + GVertex (const GVertex &v); + GVertex (db::DCoord x, db::DCoord y); + + GVertex &operator= (const GVertex &v); + +#if 0 // @@@ + bool is_outside () const; +#endif + std::vector polygons () const; + + edges_iterator begin_edges () const { return mp_edges.begin (); } + edges_iterator end_edges () const { return mp_edges.end (); } + size_t num_edges (int max_count = -1) const; + + bool has_edge (const GPolygonEdge *edge) const; + + void set_is_precious (bool f) { m_is_precious = f; } + bool is_precious () const { return m_is_precious; } + + std::string to_string (bool with_id = false) const; + + /** + * @brief Returns 1 is the point is inside the circle, 0 if on the circle and -1 if outside + * TODO: Move to db::DPoint + */ + static int in_circle (const db::DPoint &point, const db::DPoint ¢er, double radius); + + /** + * @brief Returns 1 is this point is inside the circle, 0 if on the circle and -1 if outside + */ + int in_circle (const db::DPoint ¢er, double radius) const + { + return in_circle (*this, center, radius); + } + +private: + friend class GPolygonEdge; + + void remove_edge (const edges_iterator_non_const &ec) + { + mp_edges.erase (ec); + } + + edges_type mp_edges; + bool m_is_precious; +}; + +/** + * @brief A class representing an edge in the Delaunay triangulation graph + */ +class DB_PUBLIC GPolygonEdge +{ +public: + class GPolygonIterator + { + public: + typedef GPolygon value_type; + typedef GPolygon &reference; + typedef GPolygon *pointer; + + reference operator*() const + { + return *operator-> (); + } + + pointer operator->() const + { + return m_index ? mp_edge->right () : mp_edge->left (); + } + + bool operator== (const GPolygonIterator &other) const + { + return m_index == other.m_index; + } + + bool operator!= (const GPolygonIterator &other) const + { + return !operator== (other); + } + + GPolygonIterator &operator++ () + { + while (++m_index < 2 && operator-> () == 0) + ; + return *this; + } + + private: + friend class GPolygonEdge; + + GPolygonIterator (const GPolygonEdge *edge) + : mp_edge (edge), m_index (0) + { + if (! edge) { + m_index = 2; + } else { + --m_index; + operator++ (); + } + } + + const GPolygonEdge *mp_edge; + unsigned int m_index; + }; + + GPolygonEdge (); + GPolygonEdge (GVertex *v1, GVertex *v2); + + GVertex *v1 () const { return mp_v1; } + GVertex *v2 () const { return mp_v2; } + + void reverse () + { + std::swap (mp_v1, mp_v2); + std::swap (mp_left, mp_right); + } + + GPolygon *left () const { return mp_left; } + GPolygon *right () const { return mp_right; } + + GPolygonIterator begin_polygons () const + { + return GPolygonIterator (this); + } + + GPolygonIterator end_polygons () const + { + return GPolygonIterator (0); + } + + void set_level (size_t l) { m_level = l; } + size_t level () const { return m_level; } + + void set_id (size_t id) { m_id = id; } + size_t id () const { return m_id; } + + void set_is_segment (bool is_seg) { m_is_segment = is_seg; } + bool is_segment () const { return m_is_segment; } + + std::string to_string (bool with_id = false) const; + + /** + * @brief Converts to an db::DEdge + */ + db::DEdge edge () const + { + return db::DEdge (*mp_v1, *mp_v2); + } + + /** + * @brief Returns the distance of the given point to the edge + * + * The distance is the minimum distance of the point to one point from the edge. + * TODO: Move to db::DEdge + */ + static double distance (const db::DEdge &e, const db::DPoint &p); + + /** + * @brief Returns the distance of the given point to the edge + * + * The distance is the minimum distance of the point to one point from the edge. + */ + double distance (const db::DPoint &p) const + { + return distance (edge (), p); + } + + /** + * @brief Returns a value indicating whether this edge crosses the other one + * + * "crosses" is true, if both edges share at least one point which is not an endpoint + * of one of the edges. + * TODO: Move to db::DEdge + */ + static bool crosses (const db::DEdge &e, const db::DEdge &other); + + /** + * @brief Returns a value indicating whether this edge crosses the other one + * + * "crosses" is true, if both edges share at least one point which is not an endpoint + * of one of the edges. + */ + bool crosses (const db::DEdge &other) const + { + return crosses (edge (), other); + } + + /** + * @brief Returns a value indicating whether this edge crosses the other one + * + * "crosses" is true, if both edges share at least one point which is not an endpoint + * of one of the edges. + */ + bool crosses (const db::GPolygonEdge &other) const + { + return crosses (edge (), other.edge ()); + } + + /** + * @brief Returns a value indicating whether this edge crosses the other one + * "crosses" is true, if both edges share at least one point. + * TODO: Move to db::DEdge + */ + static bool crosses_including (const db::DEdge &e, const db::DEdge &other); + + /** + * @brief Returns a value indicating whether this edge crosses the other one + * "crosses" is true, if both edges share at least one point. + */ + bool crosses_including (const db::DEdge &other) const + { + return crosses_including (edge (), other); + } + + /** + * @brief Returns a value indicating whether this edge crosses the other one + * "crosses" is true, if both edges share at least one point. + */ + bool crosses_including (const db::GPolygonEdge &other) const + { + return crosses_including (edge (), other.edge ()); + } + + /** + * @brief Gets the intersection point + * TODO: Move to db::DEdge + */ + static db::DPoint intersection_point (const db::DEdge &e, const DEdge &other); + + /** + * @brief Gets the intersection point + */ + db::DPoint intersection_point (const db::DEdge &other) const + { + return intersection_point (edge (), other); + } + + /** + * @brief Gets the intersection point + */ + db::DPoint intersection_point (const GPolygonEdge &other) const + { + return intersection_point (edge (), other.edge ()); + } + + /** + * @brief Returns a value indicating whether the point is on the edge + * TODO: Move to db::DEdge + */ + static bool point_on (const db::DEdge &edge, const db::DPoint &point); + + /** + * @brief Returns a value indicating whether the point is on the edge + */ + bool point_on (const db::DPoint &point) const + { + return point_on (edge (), point); + } + + /** + * @brief Gets the side the point is on + * + * -1 is for "left", 0 is "on" and +1 is "right" + * TODO: correct to same definition as db::Edge (negative) + */ + static int side_of (const db::DEdge &e, const db::DPoint &point) + { + return -e.side_of (point); + } + + /** + * @brief Gets the side the point is on + * + * -1 is for "left", 0 is "on" and +1 is "right" + * TODO: correct to same definition as db::Edge (negative) + */ + int side_of (const db::DPoint &p) const + { + return -edge ().side_of (p); + } + + /** + * @brief Gets the distance vector + */ + db::DVector d () const + { + return *mp_v2 - *mp_v1; + } + + /** + * @brief Gets the other triangle for the given one + */ + GPolygon *other (const GPolygon *) const; + + /** + * @brief Gets the other vertex for the given one + */ + GVertex *other (const GVertex *) const; + + /** + * @brief Gets a value indicating whether the edge has the given vertex + */ + bool has_vertex (const GVertex *) const; + + /** + * @brief Gets the common vertex of the other edge and this edge or null if there is no common vertex + */ + GVertex *common_vertex (const GPolygonEdge *other) const; + +#if 0 // @@@ + /** + * @brief Returns a value indicating whether this edge can be flipped + */ + bool can_flip () const; + + /** + * @brief Returns a value indicating whether the edge separates two triangles that can be joined into one (via the given vertex) + */ + bool can_join_via (const GVertex *vertex) const; + + /** + * @brief Returns a value indicating whether this edge is an outside edge (no other triangles) + */ + bool is_outside () const; + + /** + * @brief Returns a value indicating whether this edge belongs to outside triangles + */ + bool is_for_outside_triangles () const; +#endif // @@@ + + /** + * @brief Returns a value indicating whether t is attached to this edge + */ + bool has_polygon (const GPolygon *t) const; + +protected: + void unlink (); + void link (); + +private: + friend class GPolygon; + friend class PolygonGraph; + + GVertex *mp_v1, *mp_v2; + GPolygon *mp_left, *mp_right; + GVertex::edges_iterator_non_const m_ec_v1, m_ec_v2; + size_t m_level; + size_t m_id; + bool m_is_segment; + + void set_left (GPolygon *t); + void set_right (GPolygon *t); +}; + +/** + * @brief A compare function that compares triangles by ID + * + * The ID acts as a more predicable unique ID for the object in sets and maps. + */ +struct GPolygonEdgeLessFunc +{ + bool operator () (GPolygonEdge *a, GPolygonEdge *b) const + { + return a->id () < b->id (); + } +}; + +/** + * @brief A class representing a triangle + */ +class DB_PUBLIC GPolygon + : public tl::list_node, public tl::Object +{ +public: + GPolygon (); + + template + GPolygon (Iter from, Iter to) + : mp_e (from, to) + { + init (); + } + + ~GPolygon (); + + void unlink (); + + void set_id (size_t id) { m_id = id; } + size_t id () const { return m_id; } + + // @@@bool is_outside () const { return m_is_outside; } + // @@@void set_outside (bool o) { m_is_outside = o; } + + std::string to_string (bool with_id = false) const; + + /** + * @brief Gets the number of vertexes + */ + size_t size () const + { + return mp_v.size (); + } + + /** + * @brief Gets the nth vertex (n wraps around and can be negative) + * The vertexes are oriented clockwise. + */ + inline GVertex *vertex (int n) const + { + size_t sz = size (); + tl_assert (sz > 0); + if (n >= 0 && size_t (n) < sz) { + return mp_v[n]; + } else { + return mp_v[(n + sz) % sz]; + } + } + + /** + * @brief Gets the nth edge (n wraps around and can be negative) + */ + inline GPolygonEdge *edge (int n) const + { + size_t sz = size (); + tl_assert (sz > 0); + if (n >= 0 && size_t (n) < sz) { + return mp_e[n]; + } else { + return mp_e[(n + sz) % sz]; + } + } + + /** + * @brief Gets the area + */ + double area () const; + + /** + * @brief Returns the bounding box of the triangle + */ + db::DBox bbox () const; + +#if 0 // @@@ + /** + * @brief Gets the center point and radius of the circumcircle + * If ok is non-null, it will receive a boolean value indicating whether the circumcircle is valid. + * An invalid circumcircle is an indicator for a degenerated triangle with area 0 (or close to). + */ + std::pair circumcircle (bool *ok = 0) const; + + /** + * @brief Gets the vertex opposite of the given edge + */ + GVertex *opposite (const GPolygonEdge *edge) const; + + /** + * @brief Gets the edge opposite of the given vertex + */ + GPolygonEdge *opposite (const GVertex *vertex) const; +#endif + + /** + * @brief Gets the edge with the given vertexes + */ + GPolygonEdge *find_edge_with (const GVertex *v1, const GVertex *v2) const; + + /** + * @brief Finds the common edge for both polygons + */ + GPolygonEdge *common_edge (const GPolygon *other) const; + +#if 0 // @@@ + /** + * @brief Returns a value indicating whether the point is inside (1), on the polygon (0) or outside (-1) + */ + int contains (const db::DPoint &point) const; +#endif + + /** + * @brief Gets a value indicating whether the triangle has the given vertex + */ + inline bool has_vertex (const db::GVertex *v) const + { + for (auto i = mp_v.begin (); i != mp_v.end (); ++i) { + if (*i == v) { + return true; + } + } + return false; + } + + /** + * @brief Gets a value indicating whether the triangle has the given edge + */ + inline bool has_edge (const db::GPolygonEdge *e) const + { + for (auto i = mp_e.begin (); i != mp_e.end (); ++i) { + if (*i == e) { + return true; + } + } + return false; + } + + /** + * @brief Returns the minimum edge length + */ + double min_edge_length () const; + +#if 0 // @@@ + /** + * @brief Returns the min edge length to circumcircle radius ratio + */ + double b () const; +#endif + + /** + * @brief Returns a value indicating whether the polygon borders to a segment + */ + bool has_segment () const; + + /** + * @brief Returns the number of segments the polygon borders to + */ + unsigned int num_segments () const; + +private: + // @@@ bool m_is_outside; + std::vector mp_e; + std::vector mp_v; + size_t m_id; + + void init (); + + // no copying + GPolygon &operator= (const GPolygon &); + GPolygon (const GPolygon &); +}; + +/** + * @brief A compare function that compares polygons by ID + * + * The ID acts as a more predicable unique ID for the object in sets and maps. + */ +struct GPolygonLessFunc +{ + bool operator () (GPolygon *a, GPolygon *b) const + { + return a->id () < b->id (); + } +}; + +class DB_PUBLIC PolygonGraph +{ +public: +#if 0 // @@@ + struct TriangulateParameters + { + TriangulateParameters () + : min_b (1.0), + min_length (0.0), + max_area (0.0), + max_area_border (0.0), + max_iterations (std::numeric_limits::max ()), + base_verbosity (30), + mark_triangles (false) + { } + + /** + * @brief Min. readius-to-shortest edge ratio + */ + double min_b; + + /** + * @brief Min. edge length + * + * This parameter does not provide a guarantee about a minimume edge length, but + * helps avoiding ever-reducing triangle splits in acute corners of the input polygon. + * Splitting of edges stops when the edge is less than the min length. + */ + double min_length; + + /** + * @brief Max area or zero for "no constraint" + */ + double max_area; + + /** + * @brief Max area for border triangles or zero for "use max_area" + */ + double max_area_border; + + /** + * @brief Max number of iterations + */ + size_t max_iterations; + + /** + * @brief The verbosity level above which triangulation reports details + */ + int base_verbosity; + + /** + * @brief If true, final triangles are marked using the "id" integer as a bit field + * + * This provides information about the result quality. + * + * Bit 0: skinny triangle + * Bit 1: bad-quality (skinny or area too large) + * Bit 2: non-Delaunay (in the strict sense) + */ + bool mark_triangles; + }; +#endif + + typedef tl::list polygons_type; + typedef polygons_type::const_iterator polygon_iterator; + + PolygonGraph (); + ~PolygonGraph (); + + /** + * @brief Inserts the given polygon + */ + void insert_polygon (const db::DPolygon &box); + + /** + * @brief Returns a string representation of the polygon graph. + */ + std::string to_string (); + + /** + * @brief Returns the bounding box of the polygon graph. + */ + db::DBox bbox () const; + + /** + * @brief Iterates the polygons in the graph (begin iterator) + */ + polygon_iterator begin () const { return mp_polygons.begin (); } + + /** + * @brief Iterates the polygons in the graph (end iterator) + */ + polygon_iterator end () const { return mp_polygons.end (); } + + /** + * @brief Returns the number of polygons in the graph + */ + size_t num_polygons () const { return mp_polygons.size (); } + + /** + * @brief Clears the polygon set + */ + void clear (); + +protected: +#if 0 // @@@ + /** + * @brief Checks the polygon graph for consistency + * This method is for testing purposes mainly. + */ + bool check (bool check_delaunay = true) const; +#endif + + /** + * @brief Dumps the polygon graph to a GDS file at the given path + * This method is for testing purposes mainly. + * + * "decompose_id" will map polygons to layer 20, 21 and 22. + * according to bit 0, 1 and 2 of the ID (useful with the 'mark_polygons' + * flat in TriangulateParameters). + */ + void dump (const std::string &path, bool decompose_by_id = false) const; + + /** + * @brief Creates a new layout object representing the polygon graph + * This method is for testing purposes mainly. + */ + db::Layout *to_layout (bool decompose_by_id = false) const; + +private: + tl::list mp_polygons; + tl::stable_vector m_edges_heap; + std::vector m_returned_edges; + tl::stable_vector m_vertex_heap; +// @@@ bool m_is_constrained; +// @@@ size_t m_level; + size_t m_id; +// @@@ size_t m_flips, m_hops; + + db::GVertex *create_vertex (double x, double y); + db::GVertex *create_vertex (const db::DPoint &pt); + db::GPolygonEdge *create_edge (db::GVertex *v1, db::GVertex *v2); + + template + db::GPolygon * + create_polygon (Iter from, Iter to) + { + db::GPolygon *res = new db::GPolygon (from ,to); + res->set_id (++m_id); + mp_polygons.push_back (res); + return res; + } + + void remove_polygon (db::GPolygon *tri); + template void make_contours (const Poly &poly, const Trans &trans, std::vector > &contours); +}; + +} + +#endif + diff --git a/src/db/unit_tests/dbPolygonGraphTests.cc b/src/db/unit_tests/dbPolygonGraphTests.cc new file mode 100644 index 000000000..9fdb91a2f --- /dev/null +++ b/src/db/unit_tests/dbPolygonGraphTests.cc @@ -0,0 +1,51 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#include "dbPolygonGraph.h" +#include "tlUnitTest.h" + +#include +#include + +class TestablePolygonGraph + : public db::PolygonGraph +{ +public: + using db::PolygonGraph::PolygonGraph; + // @@@ using db::PolygonGraph::check; + using db::PolygonGraph::dump; +}; + + +TEST(basic) +{ + db::DBox box (0, 0, 100.0, 200.0); + + TestablePolygonGraph pg; + pg.insert_polygon (db::DPolygon (box)); + + // @@@ + tl::info << pg.to_string (); + pg.dump ("debug.gds"); // @@@ +} + diff --git a/src/db/unit_tests/unit_tests.pro b/src/db/unit_tests/unit_tests.pro index ecb3c7ccb..047e46ea6 100644 --- a/src/db/unit_tests/unit_tests.pro +++ b/src/db/unit_tests/unit_tests.pro @@ -12,6 +12,7 @@ SOURCES = \ dbFillToolTests.cc \ dbLogTests.cc \ dbObjectWithPropertiesTests.cc \ + dbPolygonGraphTests.cc \ dbPolygonNeighborhoodTests.cc \ dbPropertiesFilterTests.cc \ dbQuadTreeTests.cc \