This commit is contained in:
Matthias Koefferlein 2025-03-29 23:18:16 +01:00
parent e6ff30adee
commit b0f05b5327
5 changed files with 1677 additions and 0 deletions

View File

@ -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 \

846
src/db/db/dbPolygonGraph.cc Normal file
View File

@ -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 <set>
#include <memory>
#include <vector>
#include <map>
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<db::GPolygon *>
GVertex::polygons () const
{
std::set<db::GPolygon *> seen;
std::vector<db::GPolygon *> 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 &center, 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<GPolygonEdge *> e;
e.swap (mp_e);
std::multimap<db::GVertex *, GPolygonEdge *> 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<db::GPolygonEdge *> 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<db::GPolygonEdge *> 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<db::DPoint> 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<db::Layout> 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<class Poly, class Trans>
void
PolygonGraph::make_contours (const Poly &poly, const Trans &trans, std::vector<std::vector<db::GVertex *> > &edge_contours)
{
edge_contours.push_back (std::vector<db::GVertex *> ());
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<db::GVertex *> ());
for (auto pt = poly.begin_hole (h); pt != poly.end_hole (h); ++pt) {
edge_contours.back ().push_back (insert_point (trans * *pt));
}
}
}
}

777
src/db/db/dbPolygonGraph.h Normal file
View File

@ -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 <limits>
#include <list>
#include <vector>
#include <algorithm>
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<GPolygonEdge *> 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<db::GPolygon *> 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 &center, 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 &center, 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<GPolygon>, public tl::Object
{
public:
GPolygon ();
template<class Iter>
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<db::DPoint, double> 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<GPolygonEdge *> mp_e;
std::vector<db::GVertex *> 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<size_t>::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<db::GPolygon> 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<db::GPolygon> mp_polygons;
tl::stable_vector<db::GPolygonEdge> m_edges_heap;
std::vector<db::GPolygonEdge *> m_returned_edges;
tl::stable_vector<db::GVertex> 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 <class Iter>
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<class Poly, class Trans> void make_contours (const Poly &poly, const Trans &trans, std::vector<std::vector<db::GVertex *> > &contours);
};
}
#endif

View File

@ -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 <list>
#include <memory>
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"); // @@@
}

View File

@ -12,6 +12,7 @@ SOURCES = \
dbFillToolTests.cc \
dbLogTests.cc \
dbObjectWithPropertiesTests.cc \
dbPolygonGraphTests.cc \
dbPolygonNeighborhoodTests.cc \
dbPropertiesFilterTests.cc \
dbQuadTreeTests.cc \