test_quadric_edge_collapse.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. #include <catch2/catch.hpp>
  2. #include <test_utils.hpp>
  3. #include <libslic3r/QuadricEdgeCollapse.hpp>
  4. #include <libslic3r/TriangleMesh.hpp> // its - indexed_triangle_set
  5. #include "libslic3r/AABBTreeIndirect.hpp" // is similar
  6. using namespace Slic3r;
  7. namespace Private {
  8. struct Similarity
  9. {
  10. float max_distance = 0.f;
  11. float average_distance = 0.f;
  12. Similarity() = default;
  13. Similarity(float max_distance, float average_distance)
  14. : max_distance(max_distance), average_distance(average_distance)
  15. {}
  16. };
  17. // border for our algorithm with frog_leg model and decimation to 5%
  18. Similarity frog_leg_5(0.32f, 0.043f);
  19. Similarity get_similarity(const indexed_triangle_set &from,
  20. const indexed_triangle_set &to)
  21. {
  22. // create ABBTree
  23. auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
  24. from.vertices, from.indices);
  25. float sum_distance = 0.f;
  26. float max_distance = 0.f;
  27. auto collect_distances = [&](const Vec3f &surface_point) {
  28. size_t hit_idx;
  29. Vec3f hit_point;
  30. float distance2 =
  31. AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
  32. from.vertices, from.indices, tree, surface_point, hit_idx,
  33. hit_point);
  34. float distance = sqrt(distance2);
  35. if (max_distance < distance) max_distance = distance;
  36. sum_distance += distance;
  37. };
  38. for (const Vec3f &vertex : to.vertices) { collect_distances(vertex); }
  39. for (const Vec3i &t : to.indices) {
  40. Vec3f center(0, 0, 0);
  41. for (size_t i = 0; i < 3; ++i) { center += to.vertices[t[i]] / 3; }
  42. collect_distances(center);
  43. }
  44. size_t count = to.vertices.size() + to.indices.size();
  45. float average_distance = sum_distance / count;
  46. std::cout << "max_distance = " << max_distance << ", average_distance = " << average_distance << std::endl;
  47. return Similarity(max_distance, average_distance);
  48. }
  49. void is_better_similarity(const indexed_triangle_set &its_first,
  50. const indexed_triangle_set &its_second,
  51. const Similarity & compare)
  52. {
  53. Similarity s1 = get_similarity(its_first, its_second);
  54. Similarity s2 = get_similarity(its_second, its_first);
  55. CHECK(s1.average_distance < compare.average_distance);
  56. CHECK(s1.max_distance < compare.max_distance);
  57. CHECK(s2.average_distance < compare.average_distance);
  58. CHECK(s2.max_distance < compare.max_distance);
  59. }
  60. void is_worse_similarity(const indexed_triangle_set &its_first,
  61. const indexed_triangle_set &its_second,
  62. const Similarity & compare)
  63. {
  64. Similarity s1 = get_similarity(its_first, its_second);
  65. Similarity s2 = get_similarity(its_second, its_first);
  66. if (s1.max_distance < compare.max_distance &&
  67. s2.max_distance < compare.max_distance)
  68. CHECK(false);
  69. }
  70. bool exist_triangle_with_twice_vertices(const std::vector<stl_triangle_vertex_indices> &indices)
  71. {
  72. for (const auto &face : indices)
  73. if (face[0] == face[1] || face[0] == face[2] || face[1] == face[2])
  74. return true;
  75. return false;
  76. }
  77. } // namespace Private
  78. TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]")
  79. {
  80. indexed_triangle_set its;
  81. its.vertices = {Vec3f(-1.f, 0.f, 0.f), Vec3f(0.f, 1.f, 0.f),
  82. Vec3f(1.f, 0.f, 0.f), Vec3f(0.f, 0.f, 1.f),
  83. // vertex to be removed
  84. Vec3f(0.9f, .1f, -.1f)};
  85. its.indices = {Vec3i(1, 0, 3), Vec3i(2, 1, 3), Vec3i(0, 2, 3),
  86. Vec3i(0, 1, 4), Vec3i(1, 2, 4), Vec3i(2, 0, 4)};
  87. // edge to remove is between vertices 2 and 4 on trinagles 4 and 5
  88. indexed_triangle_set its_ = its; // copy
  89. // its_write_obj(its, "tetrhedron_in.obj");
  90. uint32_t wanted_count = its.indices.size() - 1;
  91. its_quadric_edge_collapse(its, wanted_count);
  92. // its_write_obj(its, "tetrhedron_out.obj");
  93. CHECK(its.indices.size() == 4);
  94. CHECK(its.vertices.size() == 4);
  95. for (size_t i = 0; i < 3; i++) {
  96. CHECK(its.indices[i] == its_.indices[i]);
  97. }
  98. for (size_t i = 0; i < 4; i++) {
  99. if (i == 2) continue;
  100. CHECK(its.vertices[i] == its_.vertices[i]);
  101. }
  102. const Vec3f &v = its.vertices[2]; // new vertex
  103. const Vec3f &v2 = its_.vertices[2]; // moved vertex
  104. const Vec3f &v4 = its_.vertices[4]; // removed vertex
  105. for (size_t i = 0; i < 3; i++) {
  106. bool is_between = (v[i] < v4[i] && v[i] > v2[i]) ||
  107. (v[i] > v4[i] && v[i] < v2[i]);
  108. CHECK(is_between);
  109. }
  110. Private::Similarity max_similarity(0.75f, 0.014f);
  111. Private::is_better_similarity(its, its_, max_similarity);
  112. }
  113. static bool is_equal(const std::vector<stl_vertex> &v1,
  114. const std::vector<stl_vertex> &v2,
  115. float epsilon = std::numeric_limits<float>::epsilon())
  116. {
  117. // is same count?
  118. if (v1.size() != v2.size()) return false;
  119. // check all v1 vertices
  120. for (const auto &v1_ : v1) {
  121. auto is_equal = [&v1_, epsilon](const auto &v2_) {
  122. for (size_t i = 0; i < 3; i++)
  123. if (fabs(v1_[i] - v2_[i]) > epsilon)
  124. return false;
  125. return true;
  126. };
  127. // is v1 vertex in v2 vertices?
  128. if(std::find_if(v2.begin(), v2.end(), is_equal) == v2.end()) return false;
  129. }
  130. return true;
  131. }
  132. TEST_CASE("Reduce to one triangle by Quadric Edge Collapse", "[its]")
  133. {
  134. // !!! Not work (no manifold - open edges{0-1, 1-2, 2-4, 4-5, 5-3, 3-0}):
  135. /////////////image////
  136. // * 5 //
  137. // |\ //
  138. // | \ //
  139. // 3 *--* 4 //
  140. // | /|\ //
  141. // |/ | \ //
  142. // 0 *--*--* 2 //
  143. // 1 //
  144. //////////////////////
  145. // all triangles are on a plane therefore quadric is zero and
  146. // when reduce edge between vertices 3 and 4 new vertex lay on vertex 3 not 4 !!!
  147. indexed_triangle_set its;
  148. its.vertices = {Vec3f(0.f, 0.f, 0.f), Vec3f(1.f, 0.f, 0.f),
  149. Vec3f(2.f, 0.f, 0.f), Vec3f(0.f, 1.f, 0.f),
  150. Vec3f(1.f, 1.f, 0.f), Vec3f(0.f, 2.f, 0.f)};
  151. its.indices = {Vec3i(0, 1, 4), Vec3i(1, 2, 4), Vec3i(0, 4, 3),
  152. Vec3i(3, 4, 5)};
  153. std::vector<stl_vertex> triangle_vertices = {its.vertices[0],
  154. its.vertices[2],
  155. its.vertices[5]};
  156. uint32_t wanted_count = 1;
  157. its_quadric_edge_collapse(its, wanted_count);
  158. // result should be one triangle made of vertices 0, 2, 5
  159. // NOT WORK
  160. //CHECK(its.indices.size() == wanted_count);
  161. //// check all triangle vertices
  162. //CHECK(is_equal(its.vertices, triangle_vertices));
  163. }
  164. TEST_CASE("Reduce to one tetrahedron by Quadric Edge Collapse", "[its]")
  165. {
  166. // Extend previous test to tetrahedron to make it manifold
  167. indexed_triangle_set its;
  168. its.vertices = {
  169. Vec3f(0.f, 0.f, 0.f), Vec3f(1.f, 0.f, 0.f), Vec3f(2.f, 0.f, 0.f),
  170. Vec3f(0.f, 1.f, 0.f), Vec3f(1.f, 1.f, 0.f),
  171. Vec3f(0.f, 2.f, 0.f)
  172. // tetrahedron extetion
  173. , Vec3f(0.f, 0.f, -2.f)
  174. };
  175. std::vector<stl_vertex> tetrahedron_vertices = {its.vertices[0],
  176. its.vertices[2],
  177. its.vertices[5],
  178. // tetrahedron extetion
  179. its.vertices[6]};
  180. its.indices = {Vec3i(0, 1, 4), Vec3i(1, 2, 4), Vec3i(0, 4, 3), Vec3i(3, 4, 5),
  181. // tetrahedron extetion
  182. Vec3i(4, 2, 6), Vec3i(5, 4, 6), Vec3i(3, 5, 6), Vec3i(0, 3, 6), Vec3i(1, 0, 6), Vec3i(2, 1, 6)
  183. };
  184. uint32_t wanted_count = 4;
  185. //its_write_obj(its, "tetrhedron_in.obj");
  186. its_quadric_edge_collapse(its, wanted_count);
  187. //its_write_obj(its, "tetrhedron_out.obj");
  188. // result should be tetrahedron
  189. CHECK(its.indices.size() == wanted_count);
  190. // check all tetrahedron vertices
  191. CHECK(is_equal(its.vertices, tetrahedron_vertices));
  192. }
  193. TEST_CASE("Simplify frog_legs.obj to 5% by Quadric edge collapse", "[its][quadric_edge_collapse]")
  194. {
  195. TriangleMesh mesh = load_model("frog_legs.obj");
  196. double original_volume = its_volume(mesh.its);
  197. uint32_t wanted_count = mesh.its.indices.size() * 0.05;
  198. REQUIRE_FALSE(mesh.empty());
  199. indexed_triangle_set its = mesh.its; // copy
  200. float max_error = std::numeric_limits<float>::max();
  201. its_quadric_edge_collapse(its, wanted_count, &max_error);
  202. // its_write_obj(its, "frog_legs_qec.obj");
  203. CHECK(its.indices.size() <= wanted_count);
  204. double volume = its_volume(its);
  205. CHECK(fabs(original_volume - volume) < 33.);
  206. Private::is_better_similarity(mesh.its, its, Private::frog_leg_5);
  207. }
  208. #include <libigl/igl/qslim.h>
  209. TEST_CASE("Simplify frog_legs.obj to 5% by IGL/qslim", "[]")
  210. {
  211. std::string obj_filename = "frog_legs.obj";
  212. TriangleMesh mesh = load_model(obj_filename);
  213. REQUIRE_FALSE(mesh.empty());
  214. indexed_triangle_set &its = mesh.its;
  215. //double original_volume = its_volume(its);
  216. uint32_t wanted_count = its.indices.size() * 0.05;
  217. Eigen::MatrixXd V(its.vertices.size(), 3);
  218. Eigen::MatrixXi F(its.indices.size(), 3);
  219. for (size_t j = 0; j < its.vertices.size(); ++j) {
  220. Vec3d vd = its.vertices[j].cast<double>();
  221. for (int i = 0; i < 3; ++i) V(j, i) = vd(i);
  222. }
  223. for (size_t j = 0; j < its.indices.size(); ++j) {
  224. const auto &f = its.indices[j];
  225. for (int i = 0; i < 3; ++i) F(j, i) = f(i);
  226. }
  227. size_t max_m = wanted_count;
  228. Eigen::MatrixXd U;
  229. Eigen::MatrixXi G;
  230. Eigen::VectorXi J, I;
  231. CHECK(igl::qslim(V, F, max_m, U, G, J, I));
  232. // convert to its
  233. indexed_triangle_set its_out;
  234. its_out.vertices.reserve(U.size()/3);
  235. its_out.indices.reserve(G.size()/3);
  236. size_t U_size = U.size() / 3;
  237. for (size_t i = 0; i < U_size; i++)
  238. its_out.vertices.emplace_back(U(i, 0), U(i, 1), U(i, 2));
  239. size_t G_size = G.size() / 3;
  240. for (size_t i = 0; i < G_size; i++)
  241. its_out.indices.emplace_back(G(i, 0), G(i, 1), G(i, 2));
  242. // check if algorithm is still worse than our
  243. Private::is_worse_similarity(its_out, its, Private::frog_leg_5);
  244. // its_out, its --> avg_distance: 0.0351217, max_distance 0.364316
  245. // its, its_out --> avg_distance: 0.0412358, max_distance 0.238913
  246. }
  247. TEST_CASE("Simplify trouble case", "[its]")
  248. {
  249. TriangleMesh tm = load_model("simplification.obj");
  250. REQUIRE_FALSE(tm.empty());
  251. float max_error = std::numeric_limits<float>::max();
  252. uint32_t wanted_count = 0;
  253. its_quadric_edge_collapse(tm.its, wanted_count, &max_error);
  254. CHECK(!Private::exist_triangle_with_twice_vertices(tm.its.indices));
  255. }
  256. TEST_CASE("Simplified cube should not be empty.", "[its]")
  257. {
  258. auto its = its_make_cube(1, 2, 3);
  259. float max_error = std::numeric_limits<float>::max();
  260. uint32_t wanted_count = 0;
  261. its_quadric_edge_collapse(its, wanted_count, &max_error);
  262. CHECK(!its.indices.empty());
  263. }