Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source/blender/blenlib/BLI_delaunay_2d.h6
-rw-r--r--source/blender/blenlib/intern/delaunay_2d.cc155
-rw-r--r--source/blender/blenlib/tests/BLI_delaunay_2d_test.cc372
3 files changed, 518 insertions, 15 deletions
diff --git a/source/blender/blenlib/BLI_delaunay_2d.h b/source/blender/blenlib/BLI_delaunay_2d.h
index e91726991ca..d42bd6af637 100644
--- a/source/blender/blenlib/BLI_delaunay_2d.h
+++ b/source/blender/blenlib/BLI_delaunay_2d.h
@@ -178,6 +178,8 @@ typedef enum CDT_output_type {
CDT_FULL,
/** All triangles fully enclosed by constraint edges or faces. */
CDT_INSIDE,
+ /** Like previous, but detect holes and omit those from output. */
+ CDT_INSIDE_WITH_HOLES,
/** Only point, edge, and face constraints, and their intersections. */
CDT_CONSTRAINTS,
/**
@@ -186,7 +188,9 @@ typedef enum CDT_output_type {
* #BMesh faces in Blender: that is,
* no vertex appears more than once and no isolated holes in faces.
*/
- CDT_CONSTRAINTS_VALID_BMESH
+ CDT_CONSTRAINTS_VALID_BMESH,
+ /** Like previous, but detect holes and omit those from output. */
+ CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES,
} CDT_output_type;
/**
diff --git a/source/blender/blenlib/intern/delaunay_2d.cc b/source/blender/blenlib/intern/delaunay_2d.cc
index 9444d1a29cb..eb3e64c49e6 100644
--- a/source/blender/blenlib/intern/delaunay_2d.cc
+++ b/source/blender/blenlib/intern/delaunay_2d.cc
@@ -226,6 +226,8 @@ template<typename Arith_t> struct CDTFace {
int visit_index{0};
/** Marks this face no longer used. */
bool deleted{false};
+ /** Marks this face as part of a hole. */
+ bool hole{false};
CDTFace() = default;
};
@@ -481,9 +483,9 @@ template<typename T> void cdt_draw(const std::string &label, const CDTArrangemen
* This is just for developer debugging anyway, and should never be called in production Blender.
*/
# ifdef _WIN32
- const char *drawfile = "./debug_draw.html";
+ const char *drawfile = "./cdt_debug_draw.html";
# else
- const char *drawfile = "/tmp/debug_draw.html";
+ const char *drawfile = "/tmp/cdt_debug_draw.html";
# endif
constexpr int max_draw_width = 1800;
constexpr int max_draw_height = 1600;
@@ -2364,9 +2366,6 @@ template<typename T> void remove_non_constraint_edges_leave_valid_bmesh(CDT_stat
template<typename T> void remove_outer_edges_until_constraints(CDT_state<T> *cdt_state)
{
- // LinkNode *fstack = NULL;
- // SymEdge *se, *se_start;
- // CDTFace *f, *fsym;
int visit = ++cdt_state->visit_count;
cdt_state->cdt.outer_face->visit_index = visit;
@@ -2415,6 +2414,137 @@ template<typename T> void remove_outer_edges_until_constraints(CDT_state<T> *cdt
}
}
+template<typename T> void remove_faces_in_holes(CDT_state<T> *cdt_state)
+{
+ CDTArrangement<T> *cdt = &cdt_state->cdt;
+ for (int i : cdt->faces.index_range()) {
+ CDTFace<T> *f = cdt->faces[i];
+ if (!f->deleted && f->hole) {
+ f->deleted = true;
+ SymEdge<T> *se = f->symedge;
+ SymEdge<T> *se_start = se;
+ SymEdge<T> *se_next = nullptr;
+ do {
+ BLI_assert(se != nullptr);
+ se_next = se->next; /* In case we delete this edge. */
+ if (se->edge && !is_constrained_edge(se->edge)) {
+ /* Invalidate one half of this edge. The other has will be or has been
+ * handled with the adjacent triangle is processed: it should be part of the same hole.
+ */
+ se->next = nullptr;
+ }
+ se = se_next;
+ } while (se != se_start);
+ }
+ }
+}
+
+/**
+ * Set the hole member of each CDTFace to true for each face that is detected to be part of a
+ * hole. A hole face is define as one for which, when a ray is shot from a point inside the face
+ * to infinity, it crosses an even number of constraint edges. We'll choose a ray direction that
+ * is extremely unlikely to exactly superimpose some edge, so avoiding the need to be careful
+ * about such overlaps.
+ *
+ * To improve performance, we gather together faces that should have the same winding number, and
+ * only shoot rays once.
+ */
+template<typename T> void detect_holes(CDT_state<T> *cdt_state)
+{
+ CDTArrangement<T> *cdt = &cdt_state->cdt;
+
+ /* Make it so that each face with the same visit_index is connected through a path of
+ * non-constraint edges. */
+ Vector<CDTFace<T> *> fstack;
+ Vector<CDTFace<T> *> region_rep_face;
+ for (int i : cdt->faces.index_range()) {
+ cdt->faces[i]->visit_index = -1;
+ }
+ int cur_region = -1;
+ cdt->outer_face->visit_index = -2; /* Don't visit this one. */
+ for (int i : cdt->faces.index_range()) {
+ CDTFace<T> *f = cdt->faces[i];
+ if (!f->deleted && f->symedge && f->visit_index == -1) {
+ fstack.append(f);
+ ++cur_region;
+ region_rep_face.append(f);
+ while (!fstack.is_empty()) {
+ CDTFace<T> *f = fstack.pop_last();
+ if (f->visit_index != -1) {
+ continue;
+ }
+ f->visit_index = cur_region;
+ SymEdge<T> *se_start = f->symedge;
+ SymEdge<T> *se = se_start;
+ do {
+ if (se->edge && !is_constrained_edge(se->edge)) {
+ CDTFace<T> *fsym = sym(se)->face;
+ if (fsym && !fsym->deleted && fsym->visit_index == -1) {
+ fstack.append(fsym);
+ }
+ }
+ se = se->next;
+ } while (se != se_start);
+ }
+ }
+ }
+ cdt_state->visit_count = ++cur_region; /* Good start for next use of visit_count. */
+
+ /* Now get hole status for each region_rep_face. */
+
+ /* Pick a ray end almost certain to be outside everything and in direction
+ * that is unlikely to hit a vertex or overlap an edge exactly. */
+ FatCo<T> ray_end;
+ ray_end.exact = vec2<T>(123456, 654321);
+ for (int i : region_rep_face.index_range()) {
+ CDTFace<T> *f = region_rep_face[i];
+ FatCo<T> mid;
+ mid.exact[0] = (f->symedge->vert->co.exact[0] + f->symedge->next->vert->co.exact[0] +
+ f->symedge->next->next->vert->co.exact[0]) /
+ 3;
+ mid.exact[1] = (f->symedge->vert->co.exact[1] + f->symedge->next->vert->co.exact[1] +
+ f->symedge->next->next->vert->co.exact[1]) /
+ 3;
+ int hits = 0;
+ /* TODO: Use CDT data structure here to greatly reduce search for intersections! */
+ for (const CDTEdge<T> *e : cdt->edges) {
+ if (!is_deleted_edge(e) && is_constrained_edge(e)) {
+ if (e->symedges[0].face->visit_index == e->symedges[1].face->visit_index) {
+ continue; /* Don't count hits on edges between faces in same region. */
+ }
+ auto isect = vec2<T>::isect_seg_seg(ray_end.exact,
+ mid.exact,
+ e->symedges[0].vert->co.exact,
+ e->symedges[1].vert->co.exact);
+ switch (isect.kind) {
+ case vec2<T>::isect_result::LINE_LINE_CROSS: {
+ hits++;
+ break;
+ }
+ case vec2<T>::isect_result::LINE_LINE_EXACT:
+ case vec2<T>::isect_result::LINE_LINE_NONE:
+ case vec2<T>::isect_result::LINE_LINE_COLINEAR:
+ break;
+ }
+ }
+ }
+ f->hole = (hits % 2) == 0;
+ }
+
+ /* Finally, propagate hole status to all holes of a region. */
+ for (int i : cdt->faces.index_range()) {
+ CDTFace<T> *f = cdt->faces[i];
+ int region = f->visit_index;
+ if (region < 0) {
+ continue;
+ }
+ CDTFace<T> *f_region_rep = region_rep_face[region];
+ if (i >= 0) {
+ f->hole = f_region_rep->hole;
+ }
+ }
+}
+
/**
* Remove edges and merge faces to get desired output, as per options.
* \note the cdt cannot be further changed after this.
@@ -2439,6 +2569,13 @@ void prepare_cdt_for_output(CDT_state<T> *cdt_state, const CDT_output_type outpu
}
}
+ bool need_holes = output_type == CDT_INSIDE_WITH_HOLES ||
+ output_type == CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES;
+
+ if (need_holes) {
+ detect_holes(cdt_state);
+ }
+
if (output_type == CDT_CONSTRAINTS) {
remove_non_constraint_edges(cdt_state);
}
@@ -2448,6 +2585,14 @@ void prepare_cdt_for_output(CDT_state<T> *cdt_state, const CDT_output_type outpu
else if (output_type == CDT_INSIDE) {
remove_outer_edges_until_constraints(cdt_state);
}
+ else if (output_type == CDT_INSIDE_WITH_HOLES) {
+ remove_outer_edges_until_constraints(cdt_state);
+ remove_faces_in_holes(cdt_state);
+ }
+ else if (output_type == CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES) {
+ remove_non_constraint_edges_leave_valid_bmesh(cdt_state);
+ remove_faces_in_holes(cdt_state);
+ }
}
template<typename T>
diff --git a/source/blender/blenlib/tests/BLI_delaunay_2d_test.cc b/source/blender/blenlib/tests/BLI_delaunay_2d_test.cc
index d00e8bf55fd..59c4be6d952 100644
--- a/source/blender/blenlib/tests/BLI_delaunay_2d_test.cc
+++ b/source/blender/blenlib/tests/BLI_delaunay_2d_test.cc
@@ -17,6 +17,7 @@ extern "C" {
#define DO_CPP_TESTS 1
#define DO_C_TESTS 1
+#define DO_TEXT_TESTS 0
#define DO_RANDOM_TESTS 0
#include "BLI_array.hh"
@@ -267,7 +268,7 @@ template<typename T>
void graph_draw(const std::string &label,
const Array<vec2<T>> &verts,
const Array<std::pair<int, int>> &edges,
- const Array<Vector<int>> &UNUSED(faces))
+ const Array<Vector<int>> &faces)
{
/* Would like to use BKE_tempdir_base() here, but that brings in dependence on kernel library.
* This is just for developer debugging anyway, and should never be called in production Blender.
@@ -281,7 +282,7 @@ void graph_draw(const std::string &label,
constexpr int max_draw_height = 1000;
constexpr int thin_line = 1;
constexpr int vert_radius = 3;
- constexpr bool draw_vert_labels = true;
+ constexpr bool draw_vert_labels = false;
constexpr bool draw_edge_labels = false;
if (verts.size() == 0) {
@@ -339,6 +340,15 @@ void graph_draw(const std::string &label,
"xml:space=\"preserve\"\n"
<< "width=\"" << view_width << "\" height=\"" << view_height << "\">n";
+ for (const Vector<int> &fverts : faces) {
+ f << "<polygon fill=\"azure\" stroke=\"none\"\n points=\"";
+ for (int vi : fverts) {
+ const vec2<T> &co = verts[vi];
+ f << SX(co[0]) << "," << SY(co[1]) << " ";
+ }
+ f << "\"\n />\n";
+ }
+
for (const std::pair<int, int> &e : edges) {
const vec2<T> &uco = verts[e.first];
const vec2<T> &vco = verts[e.second];
@@ -642,6 +652,110 @@ template<typename T> void lineinsquare_test()
if (DO_DRAW) {
graph_draw<T>("LineInSquare - constraints", out2.vert, out2.edge, out2.face);
}
+ CDT_result<T> out3 = delaunay_2d_calc(in, CDT_INSIDE_WITH_HOLES);
+ EXPECT_EQ(out3.vert.size(), 6);
+ EXPECT_EQ(out3.face.size(), 6);
+ if (DO_DRAW) {
+ graph_draw<T>("LineInSquare - inside with holes", out3.vert, out3.edge, out3.face);
+ }
+ CDT_result<T> out4 = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES);
+ EXPECT_EQ(out4.vert.size(), 6);
+ EXPECT_EQ(out4.face.size(), 2);
+ if (DO_DRAW) {
+ graph_draw<T>("LineInSquare - valid bmesh with holes", out4.vert, out4.edge, out4.face);
+ }
+}
+
+template<typename T> void lineholeinsquare_test()
+{
+ const char *spec = R"(10 1 2
+ -0.5 -0.5
+ 0.5 -0.5
+ -0.5 0.5
+ 0.5 0.5
+ -0.25 0.0
+ 0.25 0.0
+ -0.4 -0.4
+ 0.4 -0.4
+ 0.4 -0.3
+ -0.4 -0.3
+ 4 5
+ 0 1 3 2
+ 6 7 8 9
+ )";
+
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 10);
+ EXPECT_EQ(out.face.size(), 14);
+ if (DO_DRAW) {
+ graph_draw<T>("LineHoleInSquare - full", out.vert, out.edge, out.face);
+ }
+ CDT_result<T> out2 = delaunay_2d_calc(in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out2.vert.size(), 10);
+ EXPECT_EQ(out2.face.size(), 2);
+ if (DO_DRAW) {
+ graph_draw<T>("LineHoleInSquare - constraints", out2.vert, out2.edge, out2.face);
+ }
+ CDT_result<T> out3 = delaunay_2d_calc(in, CDT_INSIDE_WITH_HOLES);
+ EXPECT_EQ(out3.vert.size(), 10);
+ EXPECT_EQ(out3.face.size(), 12);
+ if (DO_DRAW) {
+ graph_draw<T>("LineHoleInSquare - inside with holes", out3.vert, out3.edge, out3.face);
+ }
+ CDT_result<T> out4 = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES);
+ EXPECT_EQ(out4.vert.size(), 10);
+ EXPECT_EQ(out4.face.size(), 2);
+ if (DO_DRAW) {
+ graph_draw<T>("LineHoleInSquare - valid bmesh with holes", out4.vert, out4.edge, out4.face);
+ }
+}
+
+template<typename T> void nestedholes_test()
+{
+ const char *spec = R"(12 0 3
+ -0.5 -0.5
+ 0.5 -0.5
+ -0.5 0.5
+ 0.5 0.5
+ -0.4 -0.4
+ 0.4 -0.4
+ 0.4 0.4
+ -0.4 0.4
+ -0.2 -0.2
+ 0.2 -0.2
+ 0.2 0.2
+ -0.2 0.2
+ 0 1 3 2
+ 4 7 6 5
+ 8 9 10 11
+ )";
+
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out = delaunay_2d_calc(in, CDT_FULL);
+ EXPECT_EQ(out.vert.size(), 12);
+ EXPECT_EQ(out.face.size(), 18);
+ if (DO_DRAW) {
+ graph_draw<T>("NestedHoles - full", out.vert, out.edge, out.face);
+ }
+ CDT_result<T> out2 = delaunay_2d_calc(in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out2.vert.size(), 12);
+ EXPECT_EQ(out2.face.size(), 3);
+ if (DO_DRAW) {
+ graph_draw<T>("NestedHoles - constraints", out2.vert, out2.edge, out2.face);
+ }
+ CDT_result<T> out3 = delaunay_2d_calc(in, CDT_INSIDE_WITH_HOLES);
+ EXPECT_EQ(out3.vert.size(), 12);
+ EXPECT_EQ(out3.face.size(), 10);
+ if (DO_DRAW) {
+ graph_draw<T>("NestedHoles - inside with holes", out3.vert, out3.edge, out3.face);
+ }
+ CDT_result<T> out4 = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES);
+ EXPECT_EQ(out4.vert.size(), 12);
+ EXPECT_EQ(out4.face.size(), 3);
+ if (DO_DRAW) {
+ graph_draw<T>("NestedHoles - valid bmesh with holes", out4.vert, out4.edge, out4.face);
+ }
}
template<typename T> void crosssegs_test()
@@ -1025,16 +1139,28 @@ template<typename T> void overlapfaces_test()
graph_draw<T>("OverlapFaces - inside", out2.vert, out2.edge, out2.face);
}
- CDT_result<T> out3 = delaunay_2d_calc(in, CDT_CONSTRAINTS);
- EXPECT_EQ(out3.face.size(), 4);
+ CDT_result<T> out3 = delaunay_2d_calc(in, CDT_INSIDE_WITH_HOLES);
+ EXPECT_EQ(out3.face.size(), 14);
+ if (DO_DRAW) {
+ graph_draw<T>("OverlapFaces - inside with holes", out3.vert, out3.edge, out3.face);
+ }
+
+ CDT_result<T> out4 = delaunay_2d_calc(in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out4.face.size(), 4);
if (DO_DRAW) {
- graph_draw<T>("OverlapFaces - constraints", out3.vert, out3.edge, out3.face);
+ graph_draw<T>("OverlapFaces - constraints", out4.vert, out4.edge, out4.face);
}
- CDT_result<T> out4 = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH);
- EXPECT_EQ(out4.face.size(), 5);
+ CDT_result<T> out5 = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH);
+ EXPECT_EQ(out5.face.size(), 5);
if (DO_DRAW) {
- graph_draw<T>("OverlapFaces - valid bmesh", out4.vert, out4.edge, out4.face);
+ graph_draw<T>("OverlapFaces - valid bmesh", out5.vert, out5.edge, out5.face);
+ }
+
+ CDT_result<T> out6 = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES);
+ EXPECT_EQ(out6.face.size(), 3);
+ if (DO_DRAW) {
+ graph_draw<T>("OverlapFaces - valid bmesh with holes", out6.vert, out6.edge, out6.face);
}
}
@@ -1246,6 +1372,34 @@ template<typename T> void repeattri_test()
}
}
+template<typename T> void square_o_test()
+{
+ const char *spec = R"(8 0 2
+ 0.0 0.0
+ 1.0 0.0
+ 1.0 1.0
+ 0.0 1.0
+ 0.2 0.2
+ 0.2 0.8
+ 0.8 0.8
+ 0.8 0.2
+ 0 1 2 3
+ 4 5 6 7
+ )";
+ CDT_input<T> in = fill_input_from_string<T>(spec);
+ CDT_result<T> out1 = delaunay_2d_calc(in, CDT_INSIDE_WITH_HOLES);
+ EXPECT_EQ(out1.face.size(), 8);
+ if (DO_DRAW) {
+ graph_draw<T>("Square O - inside with holes", out1.vert, out1.edge, out1.face);
+ }
+
+ CDT_result<T> out2 = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES);
+ EXPECT_EQ(out2.face.size(), 2);
+ if (DO_DRAW) {
+ graph_draw<T>("Square O - valid bmesh with holes", out2.vert, out2.edge, out2.face);
+ }
+}
+
TEST(delaunay_d, Empty)
{
empty_test<double>();
@@ -1301,6 +1455,16 @@ TEST(delaunay_d, LineInSquare)
lineinsquare_test<double>();
}
+TEST(delaunay_d, LineHoleInSquare)
+{
+ lineholeinsquare_test<double>();
+}
+
+TEST(delaunay_d, NestedHoles)
+{
+ nestedholes_test<double>();
+}
+
TEST(delaunay_d, CrossSegs)
{
crosssegs_test<double>();
@@ -1371,6 +1535,11 @@ TEST(delaunay_d, RepeatTri)
repeattri_test<double>();
}
+TEST(delaunay_d, SquareO)
+{
+ square_o_test<double>();
+}
+
# ifdef WITH_GMP
TEST(delaunay_m, Empty)
{
@@ -1426,6 +1595,16 @@ TEST(delaunay_m, LineInSquare)
lineinsquare_test<mpq_class>();
}
+TEST(delaunay_m, LineHoleInSquare)
+{
+ lineholeinsquare_test<mpq_class>();
+}
+
+TEST(delaunay_m, NestedHoles)
+{
+ nestedholes_test<mpq_class>();
+}
+
TEST(delaunay_m, CrossSegs)
{
crosssegs_test<mpq_class>();
@@ -1496,7 +1675,6 @@ TEST(delaunay_m, RepeatTri)
repeattri_test<mpq_class>();
}
# endif
-
#endif
#if DO_C_TESTS
@@ -1524,6 +1702,182 @@ TEST(delaunay_d, CintTwoFace)
}
#endif
+#if DO_TEXT_TESTS
+template<typename T>
+void text_test(int num_arc_points, int num_lets_per_line, int num_lines, CDT_output_type otype)
+{
+ constexpr bool print_timing = true;
+ /*
+ * Make something like a letter B:
+ *
+ * 4------------3
+ * | )
+ * | 12--11 )
+ * | | ) a3 ) a1
+ * | 9---10 )
+ * | )
+ * | 2
+ * | )
+ * | 8----7 )
+ * | | ) a2 ) a0
+ * | 5----6 )
+ * | )
+ * 0------------1
+ *
+ * Where the numbers are the first 13 vertices, and the rest of
+ * the vertices are in arcs a0, a1, a2, a3, each of which have
+ * num_arc_points per arc in them.
+ */
+
+ const char *b_before_arcs = R"(13 0 3
+ 0.0 0.0
+ 1.0 0.0
+ 1.0 1.5
+ 1.0 3.0
+ 0.0 3.0
+ 0.2 0.2
+ 0.6 0.2
+ 0.6 1.4
+ 0.2 1.4
+ 0.2 1.6
+ 0.6 1.6
+ 0.6 2.8
+ 0.2 2.8
+ 3 4 0 1 2
+ 6 5 8 7
+ 10 9 12 11
+ )";
+
+ CDT_input<T> b_before_arcs_in = fill_input_from_string<T>(b_before_arcs);
+ constexpr int narcs = 4;
+ int b_npts = b_before_arcs_in.vert.size() + narcs * num_arc_points;
+ constexpr int b_nfaces = 3;
+ Array<vec2<T>> b_vert(b_npts);
+ Array<Vector<int>> b_face(b_nfaces);
+ std::copy(b_before_arcs_in.vert.begin(), b_before_arcs_in.vert.end(), b_vert.begin());
+ std::copy(b_before_arcs_in.face.begin(), b_before_arcs_in.face.end(), b_face.begin());
+ if (num_arc_points > 0) {
+ b_face[0].pop_last(); // We'll add center point back between arcs for outer face.
+ for (int arc = 0; arc < narcs; ++arc) {
+ int arc_origin_vert;
+ int arc_terminal_vert;
+ bool ccw;
+ switch (arc) {
+ case 0:
+ arc_origin_vert = 1;
+ arc_terminal_vert = 2;
+ ccw = true;
+ break;
+ case 1:
+ arc_origin_vert = 2;
+ arc_terminal_vert = 3;
+ ccw = true;
+ break;
+ case 2:
+ arc_origin_vert = 7;
+ arc_terminal_vert = 6;
+ ccw = false;
+ break;
+ case 3:
+ arc_origin_vert = 11;
+ arc_terminal_vert = 10;
+ ccw = false;
+ break;
+ default:
+ BLI_assert(false);
+ }
+ vec2<T> start_co = b_vert[arc_origin_vert];
+ vec2<T> end_co = b_vert[arc_terminal_vert];
+ vec2<T> center_co = 0.5 * (start_co + end_co);
+ BLI_assert(start_co[0] == end_co[2]);
+ double radius = abs(math_to_double<T>(end_co[1] - center_co[1]));
+ double angle_delta = M_PI / (num_arc_points + 1);
+ int start_vert = b_before_arcs_in.vert.size() + arc * num_arc_points;
+ Vector<int> &face = b_face[(arc <= 1) ? 0 : arc - 1];
+ for (int i = 0; i < num_arc_points; ++i) {
+ vec2<T> delta;
+ float ang = ccw ? (-M_PI_2 + (i + 1) * angle_delta) : (M_PI_2 - (i + 1) * angle_delta);
+ delta[0] = T(radius * cos(ang));
+ delta[1] = T(radius * sin(ang));
+ b_vert[start_vert + i] = center_co + delta;
+ face.append(start_vert + i);
+ }
+ if (arc == 0) {
+ face.append(arc_terminal_vert);
+ }
+ }
+ }
+
+ CDT_input<T> in;
+ int tot_instances = num_lets_per_line * num_lines;
+ if (tot_instances == 1) {
+ in.vert = b_vert;
+ in.face = b_face;
+ }
+ else {
+ in.vert = Array<vec2<T>>(tot_instances * b_vert.size());
+ in.face = Array<Vector<int>>(tot_instances * b_face.size());
+ T cur_x = T(0);
+ T cur_y = T(0);
+ T delta_x = T(2);
+ T delta_y = T(3.25);
+ int instance = 0;
+ for (int line = 0; line < num_lines; ++line) {
+ for (int let = 0; let < num_lets_per_line; ++let) {
+ vec2<T> co_offset(cur_x, cur_y);
+ int in_v_offset = instance * b_vert.size();
+ for (int v = 0; v < b_vert.size(); ++v) {
+ in.vert[in_v_offset + v] = b_vert[v] + co_offset;
+ }
+ int in_f_offset = instance * b_face.size();
+ for (int f : b_face.index_range()) {
+ for (int fv : b_face[f]) {
+ in.face[in_f_offset + f].append(in_v_offset + fv);
+ }
+ }
+ cur_x += delta_x;
+ ++instance;
+ }
+ cur_y += delta_y;
+ cur_x = T(0);
+ }
+ }
+ in.epsilon = b_before_arcs_in.epsilon;
+ double tstart = PIL_check_seconds_timer();
+ CDT_result<T> out = delaunay_2d_calc(in, otype);
+ double tend = PIL_check_seconds_timer();
+ if (print_timing) {
+ std::cout << "time = " << tend - tstart << "\n";
+ }
+ if (DO_DRAW) {
+ std::string label = "Text arcpts=" + std::to_string(num_arc_points);
+ if (num_lets_per_line > 1) {
+ label += " linelen=" + std::to_string(num_lets_per_line);
+ }
+ if (num_lines > 1) {
+ label += " lines=" + std::to_string(num_lines);
+ }
+ graph_draw<T>(label, out.vert, out.edge, out.face);
+ }
+}
+
+TEST(delaunay_d, TextB10)
+{
+ text_test<double>(10, 1, 1, CDT_INSIDE_WITH_HOLES);
+}
+
+TEST(delaunay_d, TextB200)
+{
+ text_test<double>(200, 1, 1, CDT_INSIDE_WITH_HOLES);
+}
+
+TEST(delaunay_d, TextB10_10_10)
+{
+ text_test<double>(10, 10, 10, CDT_INSIDE_WITH_HOLES);
+}
+
+#endif
+
#if DO_RANDOM_TESTS
enum {