/* SPDX-License-Identifier: Apache-2.0 */ #include "testing/testing.h" #include "MEM_guardedalloc.h" extern "C" { #include "BLI_math.h" #include "BLI_rand.h" #include "PIL_time.h" } #include #include #include #include #define DO_CPP_TESTS 1 #define DO_C_TESTS 1 #define DO_TEXT_TESTS 0 #define DO_RANDOM_TESTS 0 #include "BLI_array.hh" #include "BLI_math_boolean.hh" #include "BLI_math_mpq.hh" #include "BLI_math_vec_mpq_types.hh" #include "BLI_vector.hh" #include "BLI_delaunay_2d.h" namespace blender::meshintersect { /* The spec should have the form: * #verts #edges #faces * [#verts lines) * [#edges lines] * ... [#faces lines] */ template CDT_input fill_input_from_string(const char *spec) { std::istringstream ss(spec); std::string line; getline(ss, line); std::istringstream hdrss(line); int nverts, nedges, nfaces; hdrss >> nverts >> nedges >> nfaces; if (nverts == 0) { return CDT_input(); } Array> verts(nverts); Array> edges(nedges); Array> faces(nfaces); int i = 0; while (i < nverts && getline(ss, line)) { std::istringstream iss(line); double dp0, dp1; iss >> dp0 >> dp1; T p0(dp0); T p1(dp1); verts[i] = vec2(p0, p1); i++; } i = 0; while (i < nedges && getline(ss, line)) { std::istringstream ess(line); int e0, e1; ess >> e0 >> e1; edges[i] = std::pair(e0, e1); i++; } i = 0; while (i < nfaces && getline(ss, line)) { std::istringstream fss(line); int v; while (fss >> v) { faces[i].append(v); } i++; } CDT_input ans; ans.vert = verts; ans.edge = edges; ans.face = faces; #ifdef WITH_GMP if (std::is_same::value) { ans.epsilon = T(0); } else { ans.epsilon = T(0.00001); } #else ans.epsilon = T(0.00001); #endif return ans; } /* Find an original index in a table mapping new to original. * Return -1 if not found. */ static int get_orig_index(const Array> &out_to_orig, int orig_index) { int n = int(out_to_orig.size()); for (int i = 0; i < n; ++i) { for (int orig : out_to_orig[i]) { if (orig == orig_index) { return i; } } } return -1; } template static double math_to_double(const T /*v*/) { BLI_assert(false); /* Need implementation for other type. */ return 0.0; } template<> double math_to_double(const double v) { return v; } #ifdef WITH_GMP template<> double math_to_double(const mpq_class v) { return v.get_d(); } #endif template static T math_abs(const T v); #ifdef WITH_GMP template<> mpq_class math_abs(const mpq_class v) { return abs(v); } #endif template<> double math_abs(const double v) { return fabs(v); } /* Find an output index corresponding to a given coordinate (approximately). * Return -1 if not found. */ template int get_vertex_by_coord(const CDT_result &out, double x, double y) { int nv = int(out.vert.size()); for (int i = 0; i < nv; ++i) { double vx = math_to_double(out.vert[i][0]); double vy = math_to_double(out.vert[i][1]); if (fabs(vx - x) <= 1e-5 && fabs(vy - y) <= 1e-5) { return i; } } return -1; } /* Find an edge between two given output vertex indices. -1 if not found, */ template int get_output_edge_index(const CDT_result &out, int out_index_1, int out_index_2) { int ne = int(out.edge.size()); for (int i = 0; i < ne; ++i) { if ((out.edge[i].first == out_index_1 && out.edge[i].second == out_index_2) || (out.edge[i].first == out_index_2 && out.edge[i].second == out_index_1)) { return i; } } return -1; } template bool output_edge_has_input_id(const CDT_result &out, int out_edge_index, int in_edge_index) { return out_edge_index < int(out.edge_orig.size()) && out.edge_orig[out_edge_index].contains(in_edge_index); } /* Which out face is for a give output vertex ngon? -1 if not found. * Allow for cyclic shifts vertices of one poly vs the other. */ template int get_output_face_index(const CDT_result &out, const Array &poly) { int nf = int(out.face.size()); int npolyv = int(poly.size()); for (int f = 0; f < nf; ++f) { if (out.face[f].size() != poly.size()) { continue; } for (int cycle_start = 0; cycle_start < npolyv; ++cycle_start) { bool ok = true; for (int k = 0; ok && k < npolyv; ++k) { if (out.face[f][(cycle_start + k) % npolyv] != poly[k]) { ok = false; } } if (ok) { return f; } } } return -1; } template int get_output_tri_index(const CDT_result &out, int out_index_1, int out_index_2, int out_index_3) { Array tri{out_index_1, out_index_2, out_index_3}; return get_output_face_index(out, tri); } template bool output_face_has_input_id(const CDT_result &out, int out_face_index, int in_face_index) { return out_face_index < int(out.face_orig.size()) && out.face_orig[out_face_index].contains(in_face_index); } /* For debugging. */ template std::ostream &operator<<(std::ostream &os, const CDT_result &r) { os << "\nRESULT\n"; os << r.vert.size() << " verts, " << r.edge.size() << " edges, " << r.face.size() << " faces\n"; os << "\nVERTS\n"; for (int i : r.vert.index_range()) { os << "v" << i << " = " << r.vert[i] << "\n"; os << " orig: "; for (int j : r.vert_orig[i].index_range()) { os << r.vert_orig[i][j] << " "; } os << "\n"; } os << "\nEDGES\n"; for (int i : r.edge.index_range()) { os << "e" << i << " = (" << r.edge[i].first << ", " << r.edge[i].second << ")\n"; os << " orig: "; for (int j : r.edge_orig[i].index_range()) { os << r.edge_orig[i][j] << " "; } os << "\n"; } os << "\nFACES\n"; for (int i : r.face.index_range()) { os << "f" << i << " = "; for (int j : r.face[i].index_range()) { os << r.face[i][j] << " "; } os << "\n"; os << " orig: "; for (int j : r.face_orig[i].index_range()) { os << r.face_orig[i][j] << " "; } os << "\n"; } return os; } static bool draw_append = false; /* Will be set to true after first call. */ template void graph_draw(const std::string &label, const Array> &verts, const Array> &edges, const Array> &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. */ #ifdef WIN32 constexpr const char *drawfile = "./cdt_test_draw.html"; #else constexpr const char *drawfile = "/tmp/cdt_test_draw.html"; #endif constexpr int max_draw_width = 1400; constexpr int max_draw_height = 1000; constexpr int thin_line = 1; constexpr int vert_radius = 3; constexpr bool draw_vert_labels = false; constexpr bool draw_edge_labels = false; if (verts.size() == 0) { return; } vec2 vmin(1e10, 1e10); vec2 vmax(-1e10, -1e10); for (const vec2 &v : verts) { for (int i = 0; i < 2; ++i) { double dvi = math_to_double(v[i]); if (dvi < vmin[i]) { vmin[i] = dvi; } if (dvi > vmax[i]) { vmax[i] = dvi; } } } double draw_margin = ((vmax.x - vmin.x) + (vmax.y - vmin.y)) * 0.05; double minx = vmin.x - draw_margin; double maxx = vmax.x + draw_margin; double miny = vmin.y - draw_margin; double maxy = vmax.y + draw_margin; double width = maxx - minx; double height = maxy - miny; double aspect = height / width; int view_width = max_draw_width; int view_height = int(view_width * aspect); if (view_height > max_draw_height) { view_height = max_draw_height; view_width = int(view_height / aspect); } double scale = view_width / width; #define SX(x) ((math_to_double(x) - minx) * scale) #define SY(y) ((maxy - math_to_double(y)) * scale) std::ofstream f; if (draw_append) { f.open(drawfile, std::ios_base::app); } else { f.open(drawfile); } if (!f) { std::cout << "Could not open file " << drawfile << "\n"; return; } f << "
" << label << "
\n
\n" << "n"; for (const Vector &fverts : faces) { f << " &co = verts[vi]; f << SX(co[0]) << "," << SY(co[1]) << " "; } f << "\"\n />\n"; } for (const std::pair &e : edges) { const vec2 &uco = verts[e.first]; const vec2 &vco = verts[e.second]; int strokew = thin_line; f << R"(\n"; f << " [" << e.first << "][" << e.second << "]\n"; f << "\n"; if (draw_edge_labels) { f << ")"; f << "[" << e.first << "][" << e.second << "]\n"; } } int i = 0; for (const vec2 &vco : verts) { f << R"(\n"; f << " [" << i << "]" << vco << "\n"; f << "\n"; if (draw_vert_labels) { f << "[)" << i << "]\n"; } ++i; } draw_append = true; #undef SX #undef SY } /* Should tests draw their output to an html file? */ constexpr bool DO_DRAW = false; template void expect_coord_near(const vec2 &testco, const vec2 &refco); #ifdef WITH_GMP template<> void expect_coord_near(const vec2 &testco, const vec2 &refco) { EXPECT_EQ(testco[0], refco[0]); EXPECT_EQ(testco[0], refco[0]); } #endif template<> void expect_coord_near(const vec2 &testco, const vec2 &refco) { EXPECT_NEAR(testco[0], refco[0], 1e-5); EXPECT_NEAR(testco[1], refco[1], 1e-5); } #if DO_CPP_TESTS template void empty_test() { CDT_input in; CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(0, out.vert.size()); EXPECT_EQ(0, out.edge.size()); EXPECT_EQ(0, out.face.size()); EXPECT_EQ(0, out.vert_orig.size()); EXPECT_EQ(0, out.edge_orig.size()); EXPECT_EQ(0, out.face_orig.size()); } template void onept_test() { const char *spec = R"(1 0 0 0.0 0.0 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 1); EXPECT_EQ(out.edge.size(), 0); EXPECT_EQ(out.face.size(), 0); if (out.vert.size() >= 1) { expect_coord_near(out.vert[0], vec2(0, 0)); } } template void twopt_test() { const char *spec = R"(2 0 0 0.0 -0.75 0.0 0.75 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 2); EXPECT_EQ(out.edge.size(), 1); EXPECT_EQ(out.face.size(), 0); int v0_out = get_orig_index(out.vert_orig, 0); int v1_out = get_orig_index(out.vert_orig, 1); EXPECT_NE(v0_out, -1); EXPECT_NE(v1_out, -1); EXPECT_NE(v0_out, v1_out); if (out.vert.size() >= 1) { expect_coord_near(out.vert[v0_out], vec2(0.0, -0.75)); expect_coord_near(out.vert[v1_out], vec2(0.0, 0.75)); } int e0_out = get_output_edge_index(out, v0_out, v1_out); EXPECT_EQ(e0_out, 0); if (DO_DRAW) { graph_draw("TwoPt", out.vert, out.edge, out.face); } } template void threept_test() { const char *spec = R"(3 0 0 -0.1 -0.75 0.1 0.75 0.5 0.5 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 3); EXPECT_EQ(out.edge.size(), 3); EXPECT_EQ(out.face.size(), 1); int v0_out = get_orig_index(out.vert_orig, 0); int v1_out = get_orig_index(out.vert_orig, 1); int v2_out = get_orig_index(out.vert_orig, 2); EXPECT_TRUE(v0_out != -1 && v1_out != -1 && v2_out != -1); EXPECT_TRUE(v0_out != v1_out && v0_out != v2_out && v1_out != v2_out); int e0_out = get_output_edge_index(out, v0_out, v1_out); int e1_out = get_output_edge_index(out, v1_out, v2_out); int e2_out = get_output_edge_index(out, v2_out, v0_out); EXPECT_TRUE(e0_out != -1 && e1_out != -1 && e2_out != -1); EXPECT_TRUE(e0_out != e1_out && e0_out != e2_out && e1_out != e2_out); int f0_out = get_output_tri_index(out, v0_out, v2_out, v1_out); EXPECT_EQ(f0_out, 0); if (DO_DRAW) { graph_draw("ThreePt", out.vert, out.edge, out.face); } } template void mixedpts_test() { /* Edges form a chain of length 3. */ const char *spec = R"(4 3 0 0.0 0.0 -0.5 -0.5 -0.4 -0.25 -0.3 0.8 0 1 1 2 2 3 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 4); EXPECT_EQ(out.edge.size(), 6); int v0_out = get_orig_index(out.vert_orig, 0); int v1_out = get_orig_index(out.vert_orig, 1); int v2_out = get_orig_index(out.vert_orig, 2); int v3_out = get_orig_index(out.vert_orig, 3); EXPECT_TRUE(v0_out != -1 && v1_out != -1 && v2_out != -1 && v3_out != -1); int e0_out = get_output_edge_index(out, v0_out, v1_out); int e1_out = get_output_edge_index(out, v1_out, v2_out); int e2_out = get_output_edge_index(out, v2_out, v3_out); EXPECT_TRUE(e0_out != -1 && e1_out != -1 && e2_out != -1); EXPECT_TRUE(output_edge_has_input_id(out, e0_out, 0)); EXPECT_TRUE(output_edge_has_input_id(out, e1_out, 1)); EXPECT_TRUE(output_edge_has_input_id(out, e2_out, 2)); if (DO_DRAW) { graph_draw("MixedPts", out.vert, out.edge, out.face); } } template void quad0_test() { const char *spec = R"(4 0 0 0.0 1.0 1.0 0.0 2.0 0.1 2.25 0.5 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 4); EXPECT_EQ(out.edge.size(), 5); int e_diag_out = get_output_edge_index(out, 1, 3); EXPECT_NE(e_diag_out, -1); if (DO_DRAW) { graph_draw("Quad0", out.vert, out.edge, out.face); } } template void quad1_test() { const char *spec = R"(4 0 0 0.0 0.0 0.9 -1.0 2.0 0.0 0.9 3.0 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 4); EXPECT_EQ(out.edge.size(), 5); int e_diag_out = get_output_edge_index(out, 0, 2); EXPECT_NE(e_diag_out, -1); if (DO_DRAW) { graph_draw("Quad1", out.vert, out.edge, out.face); } } template void quad2_test() { const char *spec = R"(4 0 0 0.5 0.0 0.15 0.2 0.3 0.4 .45 0.35 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 4); EXPECT_EQ(out.edge.size(), 5); int e_diag_out = get_output_edge_index(out, 1, 3); EXPECT_NE(e_diag_out, -1); if (DO_DRAW) { graph_draw("Quad2", out.vert, out.edge, out.face); } } template void quad3_test() { const char *spec = R"(4 0 0 0.5 0.0 0.0 0.0 0.3 0.4 .45 0.35 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 4); EXPECT_EQ(out.edge.size(), 5); int e_diag_out = get_output_edge_index(out, 0, 2); EXPECT_NE(e_diag_out, -1); if (DO_DRAW) { graph_draw("Quad3", out.vert, out.edge, out.face); } } template void quad4_test() { const char *spec = R"(4 0 0 1.0 1.0 0.0 0.0 1.0 -3.0 0.0 1.0 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 4); EXPECT_EQ(out.edge.size(), 5); int e_diag_out = get_output_edge_index(out, 0, 1); EXPECT_NE(e_diag_out, -1); if (DO_DRAW) { graph_draw("Quad4", out.vert, out.edge, out.face); } } template void lineinsquare_test() { const char *spec = R"(6 1 1 -0.5 -0.5 0.5 -0.5 -0.5 0.5 0.5 0.5 -0.25 0.0 0.25 0.0 4 5 0 1 3 2 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 6); EXPECT_EQ(out.face.size(), 6); if (DO_DRAW) { graph_draw("LineInSquare - full", out.vert, out.edge, out.face); } CDT_result out2 = delaunay_2d_calc(in, CDT_CONSTRAINTS); EXPECT_EQ(out2.vert.size(), 6); EXPECT_EQ(out2.face.size(), 1); if (DO_DRAW) { graph_draw("LineInSquare - constraints", out2.vert, out2.edge, out2.face); } CDT_result 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("LineInSquare - inside with holes", out3.vert, out3.edge, out3.face); } CDT_result 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("LineInSquare - valid bmesh with holes", out4.vert, out4.edge, out4.face); } } template 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 in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 10); EXPECT_EQ(out.face.size(), 14); if (DO_DRAW) { graph_draw("LineHoleInSquare - full", out.vert, out.edge, out.face); } CDT_result out2 = delaunay_2d_calc(in, CDT_CONSTRAINTS); EXPECT_EQ(out2.vert.size(), 10); EXPECT_EQ(out2.face.size(), 2); if (DO_DRAW) { graph_draw("LineHoleInSquare - constraints", out2.vert, out2.edge, out2.face); } CDT_result 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("LineHoleInSquare - inside with holes", out3.vert, out3.edge, out3.face); } CDT_result 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("LineHoleInSquare - valid bmesh with holes", out4.vert, out4.edge, out4.face); } } template 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 in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 12); EXPECT_EQ(out.face.size(), 18); if (DO_DRAW) { graph_draw("NestedHoles - full", out.vert, out.edge, out.face); } CDT_result out2 = delaunay_2d_calc(in, CDT_CONSTRAINTS); EXPECT_EQ(out2.vert.size(), 12); EXPECT_EQ(out2.face.size(), 3); if (DO_DRAW) { graph_draw("NestedHoles - constraints", out2.vert, out2.edge, out2.face); } CDT_result 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("NestedHoles - inside with holes", out3.vert, out3.edge, out3.face); } CDT_result 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("NestedHoles - valid bmesh with holes", out4.vert, out4.edge, out4.face); } } template void crosssegs_test() { const char *spec = R"(4 2 0 -0.5 0.0 0.5 0.0 -0.4 -0.5 0.4 0.5 0 1 2 3 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 5); EXPECT_EQ(out.edge.size(), 8); EXPECT_EQ(out.face.size(), 4); int v0_out = get_orig_index(out.vert_orig, 0); int v1_out = get_orig_index(out.vert_orig, 1); int v2_out = get_orig_index(out.vert_orig, 2); int v3_out = get_orig_index(out.vert_orig, 3); EXPECT_TRUE(v0_out != -1 && v1_out != -1 && v2_out != -1 && v3_out != -1); if (out.vert.size() == 5) { int v_intersect = -1; for (int i = 0; i < 5; i++) { if (!ELEM(i, v0_out, v1_out, v2_out, v3_out)) { EXPECT_EQ(v_intersect, -1); v_intersect = i; } } EXPECT_NE(v_intersect, -1); if (v_intersect != -1) { expect_coord_near(out.vert[v_intersect], vec2(0, 0)); } } if (DO_DRAW) { graph_draw("CrossSegs", out.vert, out.edge, out.face); } } template void cutacrosstri_test() { /* Right triangle with horizontal segment exactly crossing in the middle. */ const char *spec = R"(5 1 1 0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.5 0.5 0.5 3 4 0 1 2 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 5); EXPECT_EQ(out.edge.size(), 7); EXPECT_EQ(out.face.size(), 3); int v0_out = get_orig_index(out.vert_orig, 0); int v1_out = get_orig_index(out.vert_orig, 1); int v2_out = get_orig_index(out.vert_orig, 2); int v3_out = get_orig_index(out.vert_orig, 3); int v4_out = get_orig_index(out.vert_orig, 4); EXPECT_TRUE(v0_out != -1 && v1_out != -1 && v2_out != -1 && v3_out != -1 && v4_out != -1); if (out.face.size() == 3) { int e0_out = get_orig_index(out.edge_orig, 0); EXPECT_NE(e0_out, -1); int fe0_out = get_output_edge_index(out, v0_out, v1_out); EXPECT_NE(fe0_out, -1); int fe1a_out = get_output_edge_index(out, v1_out, v4_out); EXPECT_NE(fe1a_out, -1); int fe1b_out = get_output_edge_index(out, v4_out, v2_out); EXPECT_NE(fe1b_out, -1); if (fe1a_out != 0 && fe1b_out != 0) { EXPECT_EQ(e0_out, get_orig_index(out.edge_orig, 0)); EXPECT_TRUE(out.edge_orig[fe1a_out].size() == 1 && out.edge_orig[fe1a_out][0] == 11); EXPECT_TRUE(out.edge_orig[fe1b_out].size() == 1 && out.edge_orig[fe1b_out][0] == 11); } int e_diag = get_output_edge_index(out, v0_out, v4_out); EXPECT_NE(e_diag, -1); if (e_diag != -1) { EXPECT_EQ(out.edge_orig[e_diag].size(), 0); } } if (DO_DRAW) { graph_draw("CutAcrossTri", out.vert, out.edge, out.face); } } template void diamondcross_test() { /* Diamond with constraint edge from top to bottom. Some dup verts. */ const char *spec = R"(7 5 0 0.0 0.0 1.0 3.0 2.0 0.0 1.0 -3.0 0.0 0.0 1.0 -3.0 1.0 3.0 0 1 1 2 2 3 3 4 5 6 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 4); EXPECT_EQ(out.edge.size(), 5); EXPECT_EQ(out.face.size(), 2); if (DO_DRAW) { graph_draw("DiamondCross", out.vert, out.edge, out.face); } } template void twodiamondscross_test() { const char *spec = R"(12 9 0 0.0 0.0 1.0 2.0 2.0 0.0 1.0 -2.0 0.0 0.0 3.0 0.0 4.0 2.0 5.0 0.0 4.0 -2.0 3.0 0.0 0.0 0.0 5.0 0.0 0 1 1 2 2 3 3 4 5 6 6 7 7 8 8 9 10 11 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 8); EXPECT_EQ(out.edge.size(), 15); EXPECT_EQ(out.face.size(), 8); if (out.vert.size() == 8 && out.edge.size() == 15 && out.face.size() == 8) { int v_out[12]; for (int i = 0; i < 12; ++i) { v_out[i] = get_orig_index(out.vert_orig, i); EXPECT_NE(v_out[i], -1); } EXPECT_EQ(v_out[0], v_out[4]); EXPECT_EQ(v_out[0], v_out[10]); EXPECT_EQ(v_out[5], v_out[9]); EXPECT_EQ(v_out[7], v_out[11]); int e_out[9]; for (int i = 0; i < 8; ++i) { e_out[i] = get_output_edge_index(out, v_out[in.edge[i].first], v_out[in.edge[i].second]); EXPECT_NE(e_out[i], -1); } /* there won't be a single edge for the input cross edge, but rather 3 */ EXPECT_EQ(get_output_edge_index(out, v_out[10], v_out[11]), -1); int e_cross_1 = get_output_edge_index(out, v_out[0], v_out[2]); int e_cross_2 = get_output_edge_index(out, v_out[2], v_out[5]); int e_cross_3 = get_output_edge_index(out, v_out[5], v_out[7]); EXPECT_TRUE(e_cross_1 != -1 && e_cross_2 != -1 && e_cross_3 != -1); EXPECT_TRUE(output_edge_has_input_id(out, e_cross_1, 8)); EXPECT_TRUE(output_edge_has_input_id(out, e_cross_2, 8)); EXPECT_TRUE(output_edge_has_input_id(out, e_cross_3, 8)); } if (DO_DRAW) { graph_draw("TwoDiamondsCross", out.vert, out.edge, out.face); } } template void manycross_test() { /* Input has some repetition of vertices, on purpose */ const char *spec = R"(27 21 0 0.0 0.0 6.0 9.0 15.0 18.0 35.0 13.0 43.0 18.0 57.0 12.0 69.0 10.0 78.0 0.0 91.0 0.0 107.0 22.0 123.0 0.0 0.0 0.0 10.0 -14.0 35.0 -8.0 43.0 -12.0 64.0 -13.0 78.0 0.0 91.0 0.0 102.0 -9.0 116.0 -9.0 123.0 0.0 43.0 18.0 43.0 -12.0 107.0 22.0 102.0 -9.0 0.0 0.0 123.0 0.0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 11 12 12 13 13 14 14 15 15 16 17 18 18 19 19 20 21 22 23 24 25 26 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 19); EXPECT_EQ(out.edge.size(), 46); EXPECT_EQ(out.face.size(), 28); if (DO_DRAW) { graph_draw("ManyCross", out.vert, out.edge, out.face); } } template void twoface_test() { const char *spec = R"(6 0 2 0.0 0.0 1.0 0.0 0.5 1.0 1.1 1.0 1.1 0.0 1.6 1.0 0 1 2 3 4 5 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 6); EXPECT_EQ(out.edge.size(), 9); EXPECT_EQ(out.face.size(), 4); if (out.vert.size() == 6 && out.edge.size() == 9 && out.face.size() == 4) { int v_out[6]; for (int i = 0; i < 6; i++) { v_out[i] = get_orig_index(out.vert_orig, i); EXPECT_NE(v_out[i], -1); } int f0_out = get_output_tri_index(out, v_out[0], v_out[1], v_out[2]); int f1_out = get_output_tri_index(out, v_out[3], v_out[4], v_out[5]); EXPECT_NE(f0_out, -1); EXPECT_NE(f1_out, -1); int e0_out = get_output_edge_index(out, v_out[0], v_out[1]); int e1_out = get_output_edge_index(out, v_out[1], v_out[2]); int e2_out = get_output_edge_index(out, v_out[2], v_out[0]); EXPECT_NE(e0_out, -1); EXPECT_NE(e1_out, -1); EXPECT_NE(e2_out, -1); EXPECT_TRUE(output_edge_has_input_id(out, e0_out, out.face_edge_offset + 0)); EXPECT_TRUE(output_edge_has_input_id(out, e1_out, out.face_edge_offset + 1)); EXPECT_TRUE(output_edge_has_input_id(out, e2_out, out.face_edge_offset + 2)); EXPECT_TRUE(output_face_has_input_id(out, f0_out, 0)); EXPECT_TRUE(output_face_has_input_id(out, f1_out, 1)); } if (DO_DRAW) { graph_draw("TwoFace", out.vert, out.edge, out.face); } } template void twoface2_test() { const char *spec = R"(6 0 2 0.0 0.0 4.0 4.0 -4.0 2.0 3.0 0.0 3.0 6.0 -1.0 2.0 0 1 2 3 4 5 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_INSIDE); EXPECT_EQ(out.vert.size(), 10); EXPECT_EQ(out.edge.size(), 18); EXPECT_EQ(out.face.size(), 9); if (out.vert.size() == 10 && out.edge.size() == 18 && out.face.size() == 9) { /* Input verts have no dups, so expect output ones match input ones. */ for (int i = 0; i < 6; i++) { EXPECT_EQ(get_orig_index(out.vert_orig, i), i); } int v6 = get_vertex_by_coord(out, 3.0, 3.0); EXPECT_NE(v6, -1); int v7 = get_vertex_by_coord(out, 3.0, 3.75); EXPECT_NE(v7, -1); int v8 = get_vertex_by_coord(out, 0.0, 3.0); EXPECT_NE(v8, -1); int v9 = get_vertex_by_coord(out, 1.0, 1.0); EXPECT_NE(v9, -1); /* f0 to f3 should be triangles part of input face 0, not part of input face 1. */ int f0 = get_output_tri_index(out, 0, 9, 5); EXPECT_NE(f0, -1); EXPECT_TRUE(output_face_has_input_id(out, f0, 0)); EXPECT_FALSE(output_face_has_input_id(out, f0, 1)); int f1 = get_output_tri_index(out, 0, 5, 2); EXPECT_NE(f1, -1); EXPECT_TRUE(output_face_has_input_id(out, f1, 0)); EXPECT_FALSE(output_face_has_input_id(out, f1, 1)); int f2 = get_output_tri_index(out, 2, 5, 8); EXPECT_NE(f2, -1); EXPECT_TRUE(output_face_has_input_id(out, f2, 0)); EXPECT_FALSE(output_face_has_input_id(out, f2, 1)); int f3 = get_output_tri_index(out, 6, 1, 7); EXPECT_NE(f3, -1); EXPECT_TRUE(output_face_has_input_id(out, f3, 0)); EXPECT_FALSE(output_face_has_input_id(out, f3, 1)); /* f4 and f5 should be triangles part of input face 1, not part of input face 0. */ int f4 = get_output_tri_index(out, 8, 7, 4); EXPECT_NE(f4, -1); EXPECT_FALSE(output_face_has_input_id(out, f4, 0)); EXPECT_TRUE(output_face_has_input_id(out, f4, 1)); int f5 = get_output_tri_index(out, 3, 6, 9); EXPECT_NE(f5, -1); EXPECT_FALSE(output_face_has_input_id(out, f5, 0)); EXPECT_TRUE(output_face_has_input_id(out, f5, 1)); /* f6 to f8 should be triangles part of both input faces. */ int f6 = get_output_tri_index(out, 5, 9, 6); EXPECT_NE(f6, -1); EXPECT_TRUE(output_face_has_input_id(out, f6, 0)); EXPECT_TRUE(output_face_has_input_id(out, f6, 1)); int f7 = get_output_tri_index(out, 5, 6, 7); EXPECT_NE(f7, -1); EXPECT_TRUE(output_face_has_input_id(out, f7, 0)); EXPECT_TRUE(output_face_has_input_id(out, f7, 1)); int f8 = get_output_tri_index(out, 5, 7, 8); EXPECT_NE(f8, -1); EXPECT_TRUE(output_face_has_input_id(out, f8, 0)); EXPECT_TRUE(output_face_has_input_id(out, f8, 1)); } if (DO_DRAW) { graph_draw("TwoFace2", out.vert, out.edge, out.face); } } template void overlapfaces_test() { const char *spec = R"(12 0 3 0.0 0.0 1.0 0.0 1.0 1.0 0.0 1.0 0.5 0.5 1.5 0.5 1.5 1.3 0.5 1.3 0.1 0.1 0.3 0.1 0.3 0.3 0.1 0.3 0 1 2 3 4 5 6 7 8 9 10 11 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_FULL); EXPECT_EQ(out.vert.size(), 14); EXPECT_EQ(out.edge.size(), 33); EXPECT_EQ(out.face.size(), 20); if (out.vert.size() == 14 && out.edge.size() == 33 && out.face.size() == 20) { int v_out[12]; for (int i = 0; i < 12; i++) { v_out[i] = get_orig_index(out.vert_orig, i); EXPECT_NE(v_out[i], -1); } int v_int1 = 12; int v_int2 = 13; T x = out.vert[v_int1][0] - T(1); if (math_abs(x) > in.epsilon) { v_int1 = 13; v_int2 = 12; } expect_coord_near(out.vert[v_int1], vec2(1, 0.5)); expect_coord_near(out.vert[v_int2], vec2(0.5, 1)); EXPECT_EQ(out.vert_orig[v_int1].size(), 0); EXPECT_EQ(out.vert_orig[v_int2].size(), 0); int f0_out = get_output_tri_index(out, v_out[1], v_int1, v_out[4]); EXPECT_NE(f0_out, -1); EXPECT_TRUE(output_face_has_input_id(out, f0_out, 0)); int f1_out = get_output_tri_index(out, v_out[4], v_int1, v_out[2]); EXPECT_NE(f1_out, -1); EXPECT_TRUE(output_face_has_input_id(out, f1_out, 0)); EXPECT_TRUE(output_face_has_input_id(out, f1_out, 1)); int f2_out = get_output_tri_index(out, v_out[8], v_out[9], v_out[10]); if (f2_out == -1) { f2_out = get_output_tri_index(out, v_out[8], v_out[9], v_out[11]); } EXPECT_NE(f2_out, -1); EXPECT_TRUE(output_face_has_input_id(out, f2_out, 0)); EXPECT_TRUE(output_face_has_input_id(out, f2_out, 2)); } if (DO_DRAW) { graph_draw("OverlapFaces - full", out.vert, out.edge, out.face); } /* Different output types. */ CDT_result out2 = delaunay_2d_calc(in, CDT_INSIDE); EXPECT_EQ(out2.face.size(), 18); if (DO_DRAW) { graph_draw("OverlapFaces - inside", out2.vert, out2.edge, out2.face); } CDT_result out3 = delaunay_2d_calc(in, CDT_INSIDE_WITH_HOLES); EXPECT_EQ(out3.face.size(), 14); if (DO_DRAW) { graph_draw("OverlapFaces - inside with holes", out3.vert, out3.edge, out3.face); } CDT_result out4 = delaunay_2d_calc(in, CDT_CONSTRAINTS); EXPECT_EQ(out4.face.size(), 4); if (DO_DRAW) { graph_draw("OverlapFaces - constraints", out4.vert, out4.edge, out4.face); } CDT_result out5 = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH); EXPECT_EQ(out5.face.size(), 5); if (DO_DRAW) { graph_draw("OverlapFaces - valid bmesh", out5.vert, out5.edge, out5.face); } CDT_result out6 = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES); EXPECT_EQ(out6.face.size(), 3); if (DO_DRAW) { graph_draw("OverlapFaces - valid bmesh with holes", out6.vert, out6.edge, out6.face); } } template void twosquaresoverlap_test() { const char *spec = R"(8 0 2 1.0 -1.0 -1.0 -1.0 -1.0 1.0 1.0 1.0 -1.5 1.5 0.5 1.5 0.5 -0.5 -1.5 -0.5 7 6 5 4 3 2 1 0 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH); EXPECT_EQ(out.vert.size(), 10); EXPECT_EQ(out.edge.size(), 12); EXPECT_EQ(out.face.size(), 3); if (DO_DRAW) { graph_draw("TwoSquaresOverlap", out.vert, out.edge, out.face); } } template void twofaceedgeoverlap_test() { const char *spec = R"(6 0 2 5.657 0.0 -1.414 -5.831 0.0 0.0 5.657 0.0 -2.121 -2.915 0.0 0.0 2 1 0 5 4 3 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_CONSTRAINTS); EXPECT_EQ(out.vert.size(), 5); EXPECT_EQ(out.edge.size(), 7); EXPECT_EQ(out.face.size(), 3); if (out.vert.size() == 5 && out.edge.size() == 7 && out.face.size() == 3) { int v_int = 4; int v_out[6]; for (int i = 0; i < 6; i++) { v_out[i] = get_orig_index(out.vert_orig, i); EXPECT_NE(v_out[i], -1); EXPECT_NE(v_out[i], v_int); } EXPECT_EQ(v_out[0], v_out[3]); EXPECT_EQ(v_out[2], v_out[5]); int e01 = get_output_edge_index(out, v_out[0], v_out[1]); int foff = out.face_edge_offset; EXPECT_TRUE(output_edge_has_input_id(out, e01, foff + 1)); int e1i = get_output_edge_index(out, v_out[1], v_int); EXPECT_TRUE(output_edge_has_input_id(out, e1i, foff + 0)); int ei2 = get_output_edge_index(out, v_int, v_out[2]); EXPECT_TRUE(output_edge_has_input_id(out, ei2, foff + 0)); int e20 = get_output_edge_index(out, v_out[2], v_out[0]); EXPECT_TRUE(output_edge_has_input_id(out, e20, foff + 2)); EXPECT_TRUE(output_edge_has_input_id(out, e20, 2 * foff + 2)); int e24 = get_output_edge_index(out, v_out[2], v_out[4]); EXPECT_TRUE(output_edge_has_input_id(out, e24, 2 * foff + 0)); int e4i = get_output_edge_index(out, v_out[4], v_int); EXPECT_TRUE(output_edge_has_input_id(out, e4i, 2 * foff + 1)); int ei0 = get_output_edge_index(out, v_int, v_out[0]); EXPECT_TRUE(output_edge_has_input_id(out, ei0, 2 * foff + 1)); int f02i = get_output_tri_index(out, v_out[0], v_out[2], v_int); EXPECT_NE(f02i, -1); EXPECT_TRUE(output_face_has_input_id(out, f02i, 0)); EXPECT_TRUE(output_face_has_input_id(out, f02i, 1)); int f24i = get_output_tri_index(out, v_out[2], v_out[4], v_int); EXPECT_NE(f24i, -1); EXPECT_TRUE(output_face_has_input_id(out, f24i, 1)); EXPECT_FALSE(output_face_has_input_id(out, f24i, 0)); int f10i = get_output_tri_index(out, v_out[1], v_out[0], v_int); EXPECT_NE(f10i, -1); EXPECT_TRUE(output_face_has_input_id(out, f10i, 0)); EXPECT_FALSE(output_face_has_input_id(out, f10i, 1)); } if (DO_DRAW) { graph_draw("TwoFaceEdgeOverlap", out.vert, out.edge, out.face); } } template void triintri_test() { const char *spec = R"(6 0 2 -5.65685 0.0 1.41421 -5.83095 0.0 0.0 -2.47487 -1.45774 -0.707107 -2.91548 -1.06066 -1.45774 0 1 2 3 4 5 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH); EXPECT_EQ(out.vert.size(), 6); EXPECT_EQ(out.edge.size(), 8); EXPECT_EQ(out.face.size(), 3); if (DO_DRAW) { graph_draw("TriInTri", out.vert, out.edge, out.face); } } template void diamondinsquare_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.14644660940672627 0.5 0.5 0.14644660940672627 0.8535533905932737 0.5 0.5 0.8535533905932737 0 1 2 3 4 5 6 7 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH); EXPECT_EQ(out.vert.size(), 8); EXPECT_EQ(out.edge.size(), 10); EXPECT_EQ(out.face.size(), 3); if (DO_DRAW) { graph_draw("DiamondInSquare", out.vert, out.edge, out.face); } } template void diamondinsquarewire_test() { const char *spec = R"(8 8 0 0.0 0.0 1.0 0.0 1.0 1.0 0.0 1.0 0.14644660940672627 0.5 0.5 0.14644660940672627 0.8535533905932737 0.5 0.5 0.8535533905932737 0 1 1 2 2 3 3 0 4 5 5 6 6 7 7 4 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_CONSTRAINTS); EXPECT_EQ(out.vert.size(), 8); EXPECT_EQ(out.edge.size(), 8); EXPECT_EQ(out.face.size(), 2); if (DO_DRAW) { graph_draw("DiamondInSquareWire", out.vert, out.edge, out.face); } } template void repeatedge_test() { const char *spec = R"(5 3 0 0.0 0.0 0.0 1.0 1.0 1.1 0.5 -0.5 0.5 2.5 0 1 2 3 2 3 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_CONSTRAINTS); EXPECT_EQ(out.edge.size(), 2); if (DO_DRAW) { graph_draw("RepeatEdge", out.vert, out.edge, out.face); } } template void repeattri_test() { const char *spec = R"(3 0 2 0.0 0.0 1.0 0.0 0.5 1.0 0 1 2 0 1 2 )"; CDT_input in = fill_input_from_string(spec); CDT_result out = delaunay_2d_calc(in, CDT_CONSTRAINTS); EXPECT_EQ(out.edge.size(), 3); EXPECT_EQ(out.face.size(), 1); EXPECT_TRUE(output_face_has_input_id(out, 0, 0)); EXPECT_TRUE(output_face_has_input_id(out, 0, 1)); if (DO_DRAW) { graph_draw("RepeatTri", out.vert, out.edge, out.face); } } template 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 in = fill_input_from_string(spec); CDT_result out1 = delaunay_2d_calc(in, CDT_INSIDE_WITH_HOLES); EXPECT_EQ(out1.face.size(), 8); if (DO_DRAW) { graph_draw("Square O - inside with holes", out1.vert, out1.edge, out1.face); } CDT_result out2 = delaunay_2d_calc(in, CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES); EXPECT_EQ(out2.face.size(), 2); if (DO_DRAW) { graph_draw("Square O - valid bmesh with holes", out2.vert, out2.edge, out2.face); } } TEST(delaunay_d, Empty) { empty_test(); } TEST(delaunay_d, OnePt) { onept_test(); } TEST(delaunay_d, TwoPt) { twopt_test(); } TEST(delaunay_d, ThreePt) { threept_test(); } TEST(delaunay_d, MixedPts) { mixedpts_test(); } TEST(delaunay_d, Quad0) { quad0_test(); } TEST(delaunay_d, Quad1) { quad1_test(); } TEST(delaunay_d, Quad2) { quad2_test(); } TEST(delaunay_d, Quad3) { quad3_test(); } TEST(delaunay_d, Quad4) { quad4_test(); } TEST(delaunay_d, LineInSquare) { lineinsquare_test(); } TEST(delaunay_d, LineHoleInSquare) { lineholeinsquare_test(); } TEST(delaunay_d, NestedHoles) { nestedholes_test(); } TEST(delaunay_d, CrossSegs) { crosssegs_test(); } TEST(delaunay_d, CutAcrossTri) { cutacrosstri_test(); } TEST(delaunay_d, DiamondCross) { diamondcross_test(); } TEST(delaunay_d, TwoDiamondsCross) { twodiamondscross_test(); } TEST(delaunay_d, ManyCross) { manycross_test(); } TEST(delaunay_d, TwoFace) { twoface_test(); } TEST(delaunay_d, TwoFace2) { twoface2_test(); } TEST(delaunay_d, OverlapFaces) { overlapfaces_test(); } TEST(delaunay_d, TwoSquaresOverlap) { twosquaresoverlap_test(); } TEST(delaunay_d, TwoFaceEdgeOverlap) { twofaceedgeoverlap_test(); } TEST(delaunay_d, TriInTri) { triintri_test(); } TEST(delaunay_d, DiamondInSquare) { diamondinsquare_test(); } TEST(delaunay_d, DiamondInSquareWire) { diamondinsquarewire_test(); } TEST(delaunay_d, RepeatEdge) { repeatedge_test(); } TEST(delaunay_d, RepeatTri) { repeattri_test(); } TEST(delaunay_d, SquareO) { square_o_test(); } # ifdef WITH_GMP TEST(delaunay_m, Empty) { empty_test(); } TEST(delaunay_m, OnePt) { onept_test(); } TEST(delaunay_m, TwoPt) { twopt_test(); } TEST(delaunay_m, ThreePt) { threept_test(); } TEST(delaunay_m, MixedPts) { mixedpts_test(); } TEST(delaunay_m, Quad0) { quad0_test(); } TEST(delaunay_m, Quad1) { quad1_test(); } TEST(delaunay_m, Quad2) { quad2_test(); } TEST(delaunay_m, Quad3) { quad3_test(); } TEST(delaunay_m, Quad4) { quad4_test(); } TEST(delaunay_m, LineInSquare) { lineinsquare_test(); } TEST(delaunay_m, LineHoleInSquare) { lineholeinsquare_test(); } TEST(delaunay_m, NestedHoles) { nestedholes_test(); } TEST(delaunay_m, CrossSegs) { crosssegs_test(); } TEST(delaunay_m, CutAcrossTri) { cutacrosstri_test(); } TEST(delaunay_m, DiamondCross) { diamondcross_test(); } TEST(delaunay_m, TwoDiamondsCross) { twodiamondscross_test(); } TEST(delaunay_m, ManyCross) { manycross_test(); } TEST(delaunay_m, TwoFace) { twoface_test(); } TEST(delaunay_m, TwoFace2) { twoface2_test(); } TEST(delaunay_m, OverlapFaces) { overlapfaces_test(); } TEST(delaunay_m, TwoSquaresOverlap) { twosquaresoverlap_test(); } TEST(delaunay_m, TwoFaceEdgeOverlap) { twofaceedgeoverlap_test(); } TEST(delaunay_m, TriInTri) { triintri_test(); } TEST(delaunay_m, DiamondInSquare) { diamondinsquare_test(); } TEST(delaunay_m, DiamondInSquareWire) { diamondinsquarewire_test(); } TEST(delaunay_m, RepeatEdge) { repeatedge_test(); } TEST(delaunay_m, RepeatTri) { repeattri_test(); } # endif #endif #if DO_C_TESTS TEST(delaunay_d, CintTwoFace) { float vert_coords[][2] = { {0.0, 0.0}, {1.0, 0.0}, {0.5, 1.0}, {1.1, 1.0}, {1.1, 0.0}, {1.6, 1.0}}; int faces[] = {0, 1, 2, 3, 4, 5}; int faces_len[] = {3, 3}; int faces_start[] = {0, 3}; ::CDT_input input; input.verts_len = 6; input.edges_len = 0; input.faces_len = 2; input.vert_coords = vert_coords; input.edges = nullptr; input.faces = faces; input.faces_len_table = faces_len; input.faces_start_table = faces_start; input.epsilon = 1e-5f; input.need_ids = false; ::CDT_result *output = BLI_delaunay_2d_cdt_calc(&input, CDT_FULL); BLI_delaunay_2d_cdt_free(output); } TEST(delaunay_d, CintTwoFaceNoIds) { float vert_coords[][2] = { {0.0, 0.0}, {1.0, 0.0}, {0.5, 1.0}, {1.1, 1.0}, {1.1, 0.0}, {1.6, 1.0}}; int faces[] = {0, 1, 2, 3, 4, 5}; int faces_len[] = {3, 3}; int faces_start[] = {0, 3}; ::CDT_input input; input.verts_len = 6; input.edges_len = 0; input.faces_len = 2; input.vert_coords = vert_coords; input.edges = nullptr; input.faces = faces; input.faces_len_table = faces_len; input.faces_start_table = faces_start; input.epsilon = 1e-5f; input.need_ids = true; ::CDT_result *output = BLI_delaunay_2d_cdt_calc(&input, CDT_FULL); BLI_delaunay_2d_cdt_free(output); } #endif #if DO_TEXT_TESTS template void text_test( int arc_points_num, int lets_per_line_num, int lines_num, CDT_output_type otype, bool need_ids) { 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 * arc_points_num 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 b_before_arcs_in = fill_input_from_string(b_before_arcs); constexpr int narcs = 4; int b_npts = b_before_arcs_in.vert.size() + narcs * arc_points_num; constexpr int b_nfaces = 3; Array> b_vert(b_npts); Array> 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 (arc_points_num > 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 start_co = b_vert[arc_origin_vert]; vec2 end_co = b_vert[arc_terminal_vert]; vec2 center_co = 0.5 * (start_co + end_co); BLI_assert(start_co[0] == end_co[0]); double radius = abs(math_to_double(end_co[1] - center_co[1])); double angle_delta = M_PI / (arc_points_num + 1); int start_vert = b_before_arcs_in.vert.size() + arc * arc_points_num; Vector &face = b_face[(arc <= 1) ? 0 : arc - 1]; for (int i = 0; i < arc_points_num; ++i) { vec2 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 in; int tot_instances = lets_per_line_num * lines_num; if (tot_instances == 1) { in.vert = b_vert; in.face = b_face; } else { in.vert = Array>(tot_instances * b_vert.size()); in.face = Array>(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 < lines_num; ++line) { for (int let = 0; let < lets_per_line_num; ++let) { vec2 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; in.need_ids = need_ids; double tstart = PIL_check_seconds_timer(); CDT_result out = delaunay_2d_calc(in, otype); double tend = PIL_check_seconds_timer(); if (print_timing) { std::cout << "time = " << tend - tstart << "\n"; } if (!need_ids) { EXPECT_EQ(out.vert_orig.size(), 0); EXPECT_EQ(out.edge_orig.size(), 0); EXPECT_EQ(out.face_orig.size(), 0); } if (DO_DRAW) { std::string label = "Text arcpts=" + std::to_string(arc_points_num); if (lets_per_line_num > 1) { label += " linelen=" + std::to_string(lets_per_line_num); } if (lines_num > 1) { label += " lines=" + std::to_string(lines_num); } if (!need_ids) { label += " no_ids"; } if (otype != CDT_INSIDE_WITH_HOLES) { label += " otype=" + std::to_string(otype); } graph_draw(label, out.vert, out.edge, out.face); } } TEST(delaunay_d, TextB10) { text_test(10, 1, 1, CDT_INSIDE_WITH_HOLES, true); } TEST(delaunay_d, TextB10_noids) { text_test(10, 1, 1, CDT_INSIDE_WITH_HOLES, false); } TEST(delaunay_d, TextB10_inside) { text_test(10, 1, 1, CDT_INSIDE, true); } TEST(delaunay_d, TextB10_inside_noids) { text_test(10, 1, 1, CDT_INSIDE, false); } TEST(delaunay_d, TextB10_constraints) { text_test(10, 1, 1, CDT_CONSTRAINTS, true); } TEST(delaunay_d, TextB10_constraints_noids) { text_test(10, 1, 1, CDT_CONSTRAINTS, false); } TEST(delaunay_d, TextB10_constraints_valid_bmesh) { text_test(10, 1, 1, CDT_CONSTRAINTS_VALID_BMESH, true); } TEST(delaunay_d, TextB10_constraints_valid_bmesh_noids) { text_test(10, 1, 1, CDT_CONSTRAINTS_VALID_BMESH, false); } TEST(delaunay_d, TextB10_constraints_valid_bmesh_with_holes) { text_test(10, 1, 1, CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES, true); } TEST(delaunay_d, TextB10_constraints_valid_bmesh_with_holes_noids) { text_test(10, 1, 1, CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES, false); } TEST(delaunay_d, TextB200) { text_test(200, 1, 1, CDT_INSIDE_WITH_HOLES, true); } TEST(delaunay_d, TextB10_10_10) { text_test(10, 10, 10, CDT_INSIDE_WITH_HOLES, true); } TEST(delaunay_d, TextB10_10_10_noids) { text_test(10, 10, 10, CDT_INSIDE_WITH_HOLES, false); } # ifdef WITH_GMP TEST(delaunay_m, TextB10) { text_test(10, 1, 1, CDT_INSIDE_WITH_HOLES, true); } TEST(delaunay_m, TextB200) { text_test(200, 1, 1, CDT_INSIDE_WITH_HOLES, true); } TEST(delaunay_m, TextB10_10_10) { text_test(10, 10, 10, CDT_INSIDE_WITH_HOLES, true); } TEST(delaunay_m, TextB10_10_10_noids) { text_test(10, 10, 10, CDT_INSIDE_WITH_HOLES, false); } # endif #endif #if DO_RANDOM_TESTS enum { RANDOM_PTS, RANDOM_SEGS, RANDOM_POLY, RANDOM_TILTED_GRID, RANDOM_CIRCLE, RANDOM_TRI_BETWEEN_CIRCLES, }; template void rand_delaunay_test(int test_kind, int start_lg_size, int max_lg_size, int reps_per_size, double param, CDT_output_type otype) { constexpr bool print_timing = true; RNG *rng = BLI_rng_new(0); Array times(max_lg_size + 1); /* For powers of 2 sizes up to max_lg_size power of 2. */ for (int lg_size = start_lg_size; lg_size <= max_lg_size; ++lg_size) { int size = 1 << lg_size; times[lg_size] = 0.0; if (size == 1 && test_kind != RANDOM_PTS) { continue; } /* Do 'rep' repetitions. */ for (int rep = 0; rep < reps_per_size; ++rep) { /* First use test type and size to set npts, nedges, and nfaces. */ int npts = 0; int nedges = 0; int nfaces = 0; std::string test_label; switch (test_kind) { case RANDOM_PTS: { npts = size; test_label = std::to_string(npts) + "Random points"; } break; case RANDOM_SEGS: { npts = size; nedges = npts - 1; test_label = std::to_string(nedges) + "Random edges"; } break; case RANDOM_POLY: { npts = size; nedges = npts; test_label = "Random poly with " + std::to_string(nedges) + " edges"; } break; case RANDOM_TILTED_GRID: { /* A 'size' x 'size' grid of points, tilted by angle 'param'. * Edges will go from left ends to right ends and tops to bottoms, * so 2 x size of them. * Depending on epsilon, the vertical-ish edges may or may not go * through the intermediate vertices, but the horizontal ones always should. * 'param' is slope of tilt of vertical lines. */ npts = size * size; nedges = 2 * size; test_label = "Tilted grid " + std::to_string(npts) + "x" + std::to_string(npts) + " (tilt=" + std::to_string(param) + ")"; } break; case RANDOM_CIRCLE: { /* A circle with 'size' points, a random start angle, * and equal spacing thereafter. Will be input as one face. */ npts = size; nfaces = 1; test_label = "Circle with " + std::to_string(npts) + " points"; } break; case RANDOM_TRI_BETWEEN_CIRCLES: { /* A set of 'size' triangles, each has two random points on the unit circle, * and the third point is a random point on the circle with radius 'param'. * Each triangle will be input as a face. */ npts = 3 * size; nfaces = size; test_label = "Random " + std::to_string(nfaces) + " triangles between circles (inner radius=" + std::to_string(param) + ")"; } break; default: std::cout << "unknown delaunay test type\n"; return; } if (otype != CDT_FULL) { if (otype == CDT_INSIDE) { test_label += " (inside)"; } else if (otype == CDT_CONSTRAINTS) { test_label += " (constraints)"; } else if (otype == CDT_CONSTRAINTS_VALID_BMESH) { test_label += " (valid bmesh)"; } } CDT_input in; in.vert = Array>(npts); if (nedges > 0) { in.edge = Array>(nedges); } if (nfaces > 0) { in.face = Array>(nfaces); } /* Make vertices and edges or faces. */ switch (test_kind) { case RANDOM_PTS: case RANDOM_SEGS: case RANDOM_POLY: { for (int i = 0; i < size; i++) { in.vert[i][0] = T(BLI_rng_get_double(rng)); /* will be in range in [0,1) */ in.vert[i][1] = T(BLI_rng_get_double(rng)); if (test_kind != RANDOM_PTS) { if (i > 0) { in.edge[i - 1].first = i - 1; in.edge[i - 1].second = i; } } } if (test_kind == RANDOM_POLY) { in.edge[size - 1].first = size - 1; in.edge[size - 1].second = 0; } } break; case RANDOM_TILTED_GRID: { for (int i = 0; i < size; ++i) { for (int j = 0; j < size; ++j) { in.vert[i * size + j][0] = T(i * param + j); in.vert[i * size + j][1] = T(i); } } for (int i = 0; i < size; ++i) { /* Horizontal edges: connect `p(i,0)` to `p(i,size-1)`. */ in.edge[i].first = i * size; in.edge[i].second = i * size + size - 1; /* Vertical edges: connect `p(0,i)` to `p(size-1,i)`. */ in.edge[size + i].first = i; in.edge[size + i].second = (size - 1) * size + i; } } break; case RANDOM_CIRCLE: { double start_angle = BLI_rng_get_double(rng) * 2.0 * M_PI; double angle_delta = 2.0 * M_PI / size; for (int i = 0; i < size; i++) { in.vert[i][0] = T(cos(start_angle + i * angle_delta)); in.vert[i][1] = T(sin(start_angle + i * angle_delta)); in.face[0].append(i); } } break; case RANDOM_TRI_BETWEEN_CIRCLES: { for (int i = 0; i < size; i++) { /* Get three random angles in [0, 2pi). */ double angle1 = BLI_rng_get_double(rng) * 2.0 * M_PI; double angle2 = BLI_rng_get_double(rng) * 2.0 * M_PI; double angle3 = BLI_rng_get_double(rng) * 2.0 * M_PI; int ia = 3 * i; int ib = 3 * i + 1; int ic = 3 * i + 2; in.vert[ia][0] = T(cos(angle1)); in.vert[ia][1] = T(sin(angle1)); in.vert[ib][0] = T(cos(angle2)); in.vert[ib][1] = T(sin(angle2)); in.vert[ic][0] = T((param * cos(angle3))); in.vert[ic][1] = T((param * sin(angle3))); /* Put the coordinates in CCW order. */ in.face[i].append(ia); int orient = orient2d(in.vert[ia], in.vert[ib], in.vert[ic]); if (orient >= 0) { in.face[i].append(ib); in.face[i].append(ic); } else { in.face[i].append(ic); in.face[i].append(ib); } } } break; } /* Run the test. */ double tstart = PIL_check_seconds_timer(); CDT_result out = delaunay_2d_calc(in, otype); EXPECT_NE(out.vert.size(), 0); times[lg_size] += PIL_check_seconds_timer() - tstart; if (DO_DRAW) { graph_draw(test_label, out.vert, out.edge, out.face); } } } if (print_timing) { std::cout << "\nsize,time\n"; for (int lg_size = 0; lg_size <= max_lg_size; lg_size++) { int size = 1 << lg_size; std::cout << size << "," << times[lg_size] << "\n"; } } BLI_rng_free(rng); } TEST(delaunay_d, RandomPts) { rand_delaunay_test(RANDOM_PTS, 0, 7, 1, 0.0, CDT_FULL); } TEST(delaunay_d, RandomSegs) { rand_delaunay_test(RANDOM_SEGS, 1, 7, 1, 0.0, CDT_FULL); } TEST(delaunay_d, RandomPoly) { rand_delaunay_test(RANDOM_POLY, 1, 7, 1, 0.0, CDT_FULL); } TEST(delaunay_d, RandomPolyConstraints) { rand_delaunay_test(RANDOM_POLY, 1, 7, 1, 0.0, CDT_CONSTRAINTS); } TEST(delaunay_d, RandomPolyValidBmesh) { rand_delaunay_test(RANDOM_POLY, 1, 7, 1, 0.0, CDT_CONSTRAINTS_VALID_BMESH); } TEST(delaunay_d, Grid) { rand_delaunay_test(RANDOM_TILTED_GRID, 1, 6, 1, 0.0, CDT_FULL); } TEST(delaunay_d, TiltedGridA) { rand_delaunay_test(RANDOM_TILTED_GRID, 1, 6, 1, 1.0, CDT_FULL); } TEST(delaunay_d, TiltedGridB) { rand_delaunay_test(RANDOM_TILTED_GRID, 1, 6, 1, 0.01, CDT_FULL); } TEST(delaunay_d, RandomCircle) { rand_delaunay_test(RANDOM_CIRCLE, 1, 7, 1, 0.0, CDT_FULL); } TEST(delaunay_d, RandomTrisCircle) { rand_delaunay_test(RANDOM_TRI_BETWEEN_CIRCLES, 1, 6, 1, 0.25, CDT_FULL); } TEST(delaunay_d, RandomTrisCircleB) { rand_delaunay_test(RANDOM_TRI_BETWEEN_CIRCLES, 1, 6, 1, 1e-4, CDT_FULL); } # ifdef WITH_GMP TEST(delaunay_m, RandomPts) { rand_delaunay_test(RANDOM_PTS, 0, 7, 1, 0.0, CDT_FULL); } TEST(delaunay_m, RandomSegs) { rand_delaunay_test(RANDOM_SEGS, 1, 7, 1, 0.0, CDT_FULL); } TEST(delaunay_m, RandomPoly) { rand_delaunay_test(RANDOM_POLY, 1, 7, 1, 0.0, CDT_FULL); } TEST(delaunay_d, RandomPolyInside) { rand_delaunay_test(RANDOM_POLY, 1, 7, 1, 0.0, CDT_INSIDE); } TEST(delaunay_m, RandomPolyInside) { rand_delaunay_test(RANDOM_POLY, 1, 7, 1, 0.0, CDT_INSIDE); } TEST(delaunay_m, RandomPolyConstraints) { rand_delaunay_test(RANDOM_POLY, 1, 7, 1, 0.0, CDT_CONSTRAINTS); } TEST(delaunay_m, RandomPolyValidBmesh) { rand_delaunay_test(RANDOM_POLY, 1, 7, 1, 0.0, CDT_CONSTRAINTS_VALID_BMESH); } TEST(delaunay_m, Grid) { rand_delaunay_test(RANDOM_TILTED_GRID, 1, 6, 1, 0.0, CDT_FULL); } TEST(delaunay_m, TiltedGridA) { rand_delaunay_test(RANDOM_TILTED_GRID, 1, 6, 1, 1.0, CDT_FULL); } TEST(delaunay_m, TiltedGridB) { rand_delaunay_test(RANDOM_TILTED_GRID, 1, 6, 1, 0.01, CDT_FULL); } TEST(delaunay_m, RandomCircle) { rand_delaunay_test(RANDOM_CIRCLE, 1, 7, 1, 0.0, CDT_FULL); } TEST(delaunay_m, RandomTrisCircle) { rand_delaunay_test(RANDOM_TRI_BETWEEN_CIRCLES, 1, 6, 1, 0.25, CDT_FULL); } TEST(delaunay_m, RandomTrisCircleB) { rand_delaunay_test(RANDOM_TRI_BETWEEN_CIRCLES, 1, 6, 1, 1e-4, CDT_FULL); } # endif #endif } // namespace blender::meshintersect