Triangles: Added a more complex test case for triangulation and providing a min_length parameter for stopping infinite recursion

This commit is contained in:
Matthias Koefferlein 2023-08-19 15:25:21 +02:00
parent 31caa8a04d
commit 0a67aa361c
4 changed files with 5454 additions and 32 deletions

View File

@ -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 &region, double dbu)
constrain (edge_contours);
}
static bool is_skinny (db::Triangle *tri, const Triangles::TriangulateParameters &param)
static bool is_skinny (const db::Triangle *tri, const Triangles::TriangulateParameters &param)
{
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 &param)
static bool is_invalid (const db::Triangle *tri, const Triangles::TriangulateParameters &param)
{
if (is_skinny (tri, param)) {
return true;
@ -1517,7 +1531,7 @@ Triangles::triangulate (const db::Region &region, 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 &region, 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 &region, 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 ();
}

View File

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

View File

@ -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 &region, 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));
}

5239
testdata/algo/triangles1.txt vendored Normal file

File diff suppressed because it is too large Load Diff