mirror of https://github.com/KLayout/klayout.git
Triangles: Added a more complex test case for triangulation and providing a min_length parameter for stopping infinite recursion
This commit is contained in:
parent
31caa8a04d
commit
0a67aa361c
|
|
@ -251,7 +251,7 @@ Triangles::check (bool check_delaunay) const
|
|||
}
|
||||
|
||||
db::Layout *
|
||||
Triangles::to_layout () const
|
||||
Triangles::to_layout (bool decompose_by_id) const
|
||||
{
|
||||
db::Layout *layout = new db::Layout ();
|
||||
layout->dbu (0.001);
|
||||
|
|
@ -262,6 +262,9 @@ Triangles::to_layout () const
|
|||
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));
|
||||
|
||||
for (auto t = mp_triangles.begin (); t != mp_triangles.end (); ++t) {
|
||||
db::DPoint pts[3];
|
||||
|
|
@ -271,6 +274,17 @@ Triangles::to_layout () const
|
|||
db::DPolygon poly;
|
||||
poly.assign_hull (pts + 0, pts + 3);
|
||||
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) {
|
||||
|
|
@ -283,9 +297,9 @@ Triangles::to_layout () const
|
|||
}
|
||||
|
||||
void
|
||||
Triangles::dump (const std::string &path) const
|
||||
Triangles::dump (const std::string &path, bool decompose_by_id) const
|
||||
{
|
||||
std::unique_ptr<db::Layout> ly (to_layout ());
|
||||
std::unique_ptr<db::Layout> ly (to_layout (decompose_by_id));
|
||||
|
||||
tl::OutputStream stream (path);
|
||||
|
||||
|
|
@ -1426,7 +1440,7 @@ Triangles::create_constrained_delaunay (const db::Region ®ion, double dbu)
|
|||
constrain (edge_contours);
|
||||
}
|
||||
|
||||
static bool is_skinny (db::Triangle *tri, const Triangles::TriangulateParameters ¶m)
|
||||
static bool is_skinny (const db::Triangle *tri, const Triangles::TriangulateParameters ¶m)
|
||||
{
|
||||
if (param.min_b < db::epsilon) {
|
||||
return false;
|
||||
|
|
@ -1437,7 +1451,7 @@ static bool is_skinny (db::Triangle *tri, const Triangles::TriangulateParameters
|
|||
}
|
||||
}
|
||||
|
||||
static bool is_invalid (db::Triangle *tri, const Triangles::TriangulateParameters ¶m)
|
||||
static bool is_invalid (const db::Triangle *tri, const Triangles::TriangulateParameters ¶m)
|
||||
{
|
||||
if (is_skinny (tri, param)) {
|
||||
return true;
|
||||
|
|
@ -1517,7 +1531,7 @@ Triangles::triangulate (const db::Region ®ion, const TriangulateParameters &p
|
|||
|
||||
if ((*t)->contains (center) >= 0) {
|
||||
|
||||
// heuristics #1: never insert a point into a triangle with more than two segments
|
||||
// heuristics #1: never insert a point into a triangle with more than one segments
|
||||
if (t->get ()->num_segments () <= 1) {
|
||||
if (tl::verbosity () >= parameters.base_verbosity + 20) {
|
||||
tl::info << "Inserting in-triangle center " << center.to_string () << " of " << (*t)->to_string (true);
|
||||
|
|
@ -1549,30 +1563,34 @@ Triangles::triangulate (const db::Region ®ion, const TriangulateParameters &p
|
|||
} else {
|
||||
|
||||
double sr = edge->d ().length () * 0.5;
|
||||
db::DPoint pnew = *edge->v1 () + edge->d () * 0.5;
|
||||
if (sr >= parameters.min_length) {
|
||||
|
||||
if (tl::verbosity () >= parameters.base_verbosity + 20) {
|
||||
tl::info << "Split edge " << edge->to_string (true) << " at " << pnew.to_string ();
|
||||
}
|
||||
db::Vertex *vnew = insert_point (pnew, &new_triangles);
|
||||
auto vertexes_in_diametral_circle = find_points_around (vnew, sr);
|
||||
db::DPoint pnew = *edge->v1 () + edge->d () * 0.5;
|
||||
|
||||
std::vector<db::Vertex *> to_delete;
|
||||
for (auto v = vertexes_in_diametral_circle.begin (); v != vertexes_in_diametral_circle.end (); ++v) {
|
||||
bool has_segment = false;
|
||||
for (auto e = (*v)->begin_edges (); e != (*v)->end_edges () && ! has_segment; ++e) {
|
||||
has_segment = (*e)->is_segment ();
|
||||
if (tl::verbosity () >= parameters.base_verbosity + 20) {
|
||||
tl::info << "Split edge " << edge->to_string (true) << " at " << pnew.to_string ();
|
||||
}
|
||||
db::Vertex *vnew = insert_point (pnew, &new_triangles);
|
||||
auto vertexes_in_diametral_circle = find_points_around (vnew, sr);
|
||||
|
||||
std::vector<db::Vertex *> to_delete;
|
||||
for (auto v = vertexes_in_diametral_circle.begin (); v != vertexes_in_diametral_circle.end (); ++v) {
|
||||
bool has_segment = false;
|
||||
for (auto e = (*v)->begin_edges (); e != (*v)->end_edges () && ! has_segment; ++e) {
|
||||
has_segment = (*e)->is_segment ();
|
||||
}
|
||||
if (! has_segment) {
|
||||
to_delete.push_back (*v);
|
||||
}
|
||||
}
|
||||
|
||||
if (tl::verbosity () >= parameters.base_verbosity + 20) {
|
||||
tl::info << " -> found " << to_delete.size () << " vertexes to remove";
|
||||
}
|
||||
for (auto v = to_delete.begin (); v != to_delete.end (); ++v) {
|
||||
remove (*v, &new_triangles);
|
||||
}
|
||||
if (! has_segment) {
|
||||
to_delete.push_back (*v);
|
||||
}
|
||||
}
|
||||
|
||||
if (tl::verbosity () >= parameters.base_verbosity + 20) {
|
||||
tl::info << " -> found " << to_delete.size () << " vertexes to remove";
|
||||
}
|
||||
for (auto v = to_delete.begin (); v != to_delete.end (); ++v) {
|
||||
remove (*v, &new_triangles);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1586,6 +1604,35 @@ Triangles::triangulate (const db::Region ®ion, const TriangulateParameters &p
|
|||
if (tl::verbosity () >= parameters.base_verbosity + 20) {
|
||||
tl::info << "Finishing ..";
|
||||
}
|
||||
|
||||
if (parameters.mark_triangles) {
|
||||
|
||||
for (auto t = begin (); t != end (); ++t) {
|
||||
|
||||
size_t id = 0;
|
||||
|
||||
if (! t->is_outside ()) {
|
||||
|
||||
if (is_skinny (t.operator-> (), parameters)) {
|
||||
id |= 1;
|
||||
}
|
||||
if (is_invalid (t.operator-> (), parameters)) {
|
||||
id |= 2;
|
||||
}
|
||||
auto cp = t->circumcircle ();
|
||||
auto vi = find_inside_circle (cp.first, cp.second);
|
||||
if (! vi.empty ()) {
|
||||
id |= 4;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
(const_cast<db::Triangle *> (t.operator->()))->set_id (id);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
remove_outside_triangles ();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,10 +45,12 @@ public:
|
|||
{
|
||||
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)
|
||||
base_verbosity (30),
|
||||
mark_triangles (false)
|
||||
{ }
|
||||
|
||||
/**
|
||||
|
|
@ -56,6 +58,15 @@ public:
|
|||
*/
|
||||
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"
|
||||
*/
|
||||
|
|
@ -75,6 +86,17 @@ public:
|
|||
* @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;
|
||||
};
|
||||
|
||||
typedef tl::list<db::Triangle> triangles_type;
|
||||
|
|
@ -139,14 +161,18 @@ public:
|
|||
/**
|
||||
* @brief Dumps the triangle graph to a GDS file at the given path
|
||||
* This method is for testing purposes mainly.
|
||||
*
|
||||
* "decompose_id" will map triangles to layer 20, 21 and 22.
|
||||
* according to bit 0, 1 and 2 of the ID (useful with the 'mark_triangles'
|
||||
* flat in TriangulateParameters).
|
||||
*/
|
||||
void dump (const std::string &path) const;
|
||||
void dump (const std::string &path, bool decompose_by_id = false) const;
|
||||
|
||||
/**
|
||||
* @brief Creates a new layout object representing the triangle graph
|
||||
* This method is for testing purposes mainly.
|
||||
*/
|
||||
db::Layout *to_layout () const;
|
||||
db::Layout *to_layout (bool decompose_by_id = false) const;
|
||||
|
||||
/**
|
||||
* @brief Finds the points within (not "on") a circle of radius "radius" around the given vertex.
|
||||
|
|
|
|||
|
|
@ -22,7 +22,10 @@
|
|||
|
||||
|
||||
#include "dbTriangles.h"
|
||||
#include "dbWriter.h"
|
||||
#include "tlUnitTest.h"
|
||||
#include "tlStream.h"
|
||||
#include "tlFileUtils.h"
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
|
@ -258,9 +261,8 @@ TEST(insert_many)
|
|||
tris.insert_point (x, y);
|
||||
}
|
||||
|
||||
tl::info << "avg. flips = " << double (tris.flips ()) / double (n);
|
||||
tl::info << "avg. hops = " << double (tris.hops ()) / double (n);
|
||||
// @@@ tris.dump ("debug.gds");
|
||||
EXPECT_LT (double (tris.flips ()) / double (n), 3.0);
|
||||
EXPECT_LT (double (tris.hops ()) / double (n), 23.0);
|
||||
}
|
||||
|
||||
TEST(heavy_insert)
|
||||
|
|
@ -683,3 +685,111 @@ TEST(triangulate)
|
|||
EXPECT_GT (tri.num_triangles (), size_t (900));
|
||||
EXPECT_LT (tri.num_triangles (), size_t (1000));
|
||||
}
|
||||
|
||||
void read_polygons (const std::string &path, db::Region ®ion, double dbu)
|
||||
{
|
||||
tl::InputStream is (path);
|
||||
tl::TextInputStream ti (is);
|
||||
|
||||
unsigned int nvert = 0, nedges = 0;
|
||||
|
||||
{
|
||||
tl::Extractor ex (ti.get_line ().c_str ());
|
||||
ex.read (nvert);
|
||||
ex.read (nedges);
|
||||
}
|
||||
|
||||
std::vector<db::Point> v;
|
||||
auto dbu_trans = db::CplxTrans (dbu).inverted ();
|
||||
for (unsigned int i = 0; i < nvert; ++i) {
|
||||
double x = 0, y = 0;
|
||||
tl::Extractor ex (ti.get_line ().c_str ());
|
||||
ex.read (x);
|
||||
ex.read (y);
|
||||
v.push_back (dbu_trans * db::DPoint (x, y));
|
||||
}
|
||||
|
||||
unsigned int nstart = 0;
|
||||
bool new_contour = true;
|
||||
std::vector<db::Point> contour;
|
||||
|
||||
for (unsigned int i = 0; i < nedges; ++i) {
|
||||
|
||||
unsigned int n1 = 0, n2 = 0;
|
||||
|
||||
tl::Extractor ex (ti.get_line ().c_str ());
|
||||
ex.read (n1);
|
||||
ex.read (n2);
|
||||
|
||||
if (new_contour) {
|
||||
nstart = n1;
|
||||
new_contour = false;
|
||||
}
|
||||
|
||||
contour.push_back (v[n1]);
|
||||
|
||||
if (n2 == nstart) {
|
||||
// finish contour
|
||||
db::SimplePolygon sp;
|
||||
sp.assign_hull (contour.begin (), contour.end ());
|
||||
region.insert (sp);
|
||||
new_contour = true;
|
||||
contour.clear ();
|
||||
} else if (n2 <= n1) {
|
||||
tl::error << "Invalid polygon wrap in line " << ti.line_number ();
|
||||
tl_assert (false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
TEST(triangulate2)
|
||||
{
|
||||
double dbu = 0.001;
|
||||
|
||||
db::Region r;
|
||||
read_polygons (tl::combine_path (tl::testsrc (), "testdata/algo/triangles1.txt"), r, dbu);
|
||||
|
||||
// for debugging purposes dump the inputs
|
||||
if (false) {
|
||||
|
||||
db::Layout layout = db::Layout ();
|
||||
layout.dbu (dbu);
|
||||
db::Cell &top = layout.cell (layout.add_cell ("DUMP"));
|
||||
unsigned int l1 = layout.insert_layer (db::LayerProperties (1, 0));
|
||||
r.insert_into (&layout, top.cell_index (), l1);
|
||||
|
||||
{
|
||||
tl::OutputStream stream ("input.gds");
|
||||
db::SaveLayoutOptions opt;
|
||||
db::Writer writer (opt);
|
||||
writer.write (layout, stream);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
db::Triangles::TriangulateParameters param;
|
||||
param.min_b = 1.0;
|
||||
param.max_area = 0.1;
|
||||
param.min_length = 0.001;
|
||||
|
||||
db::Triangles tri;
|
||||
tri.triangulate (r, param, dbu);
|
||||
|
||||
EXPECT_EQ (tri.check (false), true);
|
||||
|
||||
// for debugging:
|
||||
// tri.dump ("debug.gds", true);
|
||||
|
||||
size_t n_skinny = 0;
|
||||
for (auto t = tri.begin (); t != tri.end (); ++t) {
|
||||
EXPECT_LE (t->area (), param.max_area);
|
||||
if (t->b () < param.min_b) {
|
||||
++n_skinny;
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_LT (n_skinny, size_t (20));
|
||||
EXPECT_GT (tri.num_triangles (), size_t (29000));
|
||||
EXPECT_LT (tri.num_triangles (), size_t (30000));
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue