test_indexed_triangle_set.cpp 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. #include <iostream>
  2. #include <fstream>
  3. #include <catch2/catch.hpp>
  4. #include "libslic3r/TriangleMesh.hpp"
  5. using namespace Slic3r;
  6. TEST_CASE("Split empty mesh", "[its_split][its]") {
  7. using namespace Slic3r;
  8. indexed_triangle_set its;
  9. std::vector<indexed_triangle_set> res = its_split(its);
  10. REQUIRE(res.empty());
  11. }
  12. TEST_CASE("Split simple mesh consisting of one part", "[its_split][its]") {
  13. using namespace Slic3r;
  14. auto cube = its_make_cube(10., 10., 10.);
  15. std::vector<indexed_triangle_set> res = its_split(cube);
  16. REQUIRE(res.size() == 1);
  17. REQUIRE(res.front().indices.size() == cube.indices.size());
  18. REQUIRE(res.front().vertices.size() == cube.vertices.size());
  19. }
  20. void debug_write_obj(const std::vector<indexed_triangle_set> &res, const std::string &name)
  21. {
  22. #ifndef NDEBUG
  23. size_t part_idx = 0;
  24. for (auto &part_its : res) {
  25. its_write_obj(part_its, (name + std::to_string(part_idx++) + ".obj").c_str());
  26. }
  27. #endif
  28. }
  29. TEST_CASE("Split two non-watertight mesh", "[its_split][its]") {
  30. using namespace Slic3r;
  31. auto cube1 = its_make_cube(10., 10., 10.);
  32. cube1.indices.pop_back();
  33. auto cube2 = cube1;
  34. its_transform(cube1, identity3f().translate(Vec3f{-5.f, 0.f, 0.f}));
  35. its_transform(cube2, identity3f().translate(Vec3f{5.f, 0.f, 0.f}));
  36. its_merge(cube1, cube2);
  37. std::vector<indexed_triangle_set> res = its_split(cube1);
  38. REQUIRE(res.size() == 2);
  39. REQUIRE(res[0].indices.size() == res[1].indices.size());
  40. REQUIRE(res[0].indices.size() == cube2.indices.size());
  41. REQUIRE(res[0].vertices.size() == res[1].vertices.size());
  42. REQUIRE(res[0].vertices.size() == cube2.vertices.size());
  43. debug_write_obj(res, "parts_non_watertight");
  44. }
  45. TEST_CASE("Split non-manifold mesh", "[its_split][its]") {
  46. using namespace Slic3r;
  47. auto cube = its_make_cube(10., 10., 10.), cube_low = cube;
  48. its_transform(cube_low, identity3f().translate(Vec3f{10.f, 10.f, 10.f}));
  49. its_merge(cube, cube_low);
  50. its_merge_vertices(cube);
  51. std::vector<indexed_triangle_set> res = its_split(cube);
  52. REQUIRE(res.size() == 2);
  53. REQUIRE(res[0].indices.size() == res[1].indices.size());
  54. REQUIRE(res[0].indices.size() == cube_low.indices.size());
  55. REQUIRE(res[0].vertices.size() == res[1].vertices.size());
  56. REQUIRE(res[0].vertices.size() == cube_low.vertices.size());
  57. debug_write_obj(res, "cubes_non_manifold");
  58. }
  59. TEST_CASE("Split two watertight meshes", "[its_split][its]") {
  60. using namespace Slic3r;
  61. auto sphere1 = its_make_sphere(10., 2 * PI / 200.), sphere2 = sphere1;
  62. its_transform(sphere1, identity3f().translate(Vec3f{-5.f, 0.f, 0.f}));
  63. its_transform(sphere2, identity3f().translate(Vec3f{5.f, 0.f, 0.f}));
  64. its_merge(sphere1, sphere2);
  65. std::vector<indexed_triangle_set> res = its_split(sphere1);
  66. REQUIRE(res.size() == 2);
  67. REQUIRE(res[0].indices.size() == res[1].indices.size());
  68. REQUIRE(res[0].indices.size() == sphere2.indices.size());
  69. REQUIRE(res[0].vertices.size() == res[1].vertices.size());
  70. REQUIRE(res[0].vertices.size() == sphere2.vertices.size());
  71. debug_write_obj(res, "parts_watertight");
  72. }
  73. #include <libslic3r/QuadricEdgeCollapse.hpp>
  74. static float triangle_area(const Vec3f &v0, const Vec3f &v1, const Vec3f &v2)
  75. {
  76. Vec3f ab = v1 - v0;
  77. Vec3f ac = v2 - v0;
  78. return ab.cross(ac).norm() / 2.f;
  79. }
  80. static float triangle_area(const Vec3crd &triangle_inices, const std::vector<Vec3f> &vertices)
  81. {
  82. return triangle_area(vertices[triangle_inices[0]],
  83. vertices[triangle_inices[1]],
  84. vertices[triangle_inices[2]]);
  85. }
  86. static std::mt19937 create_random_generator() {
  87. std::random_device rd;
  88. std::mt19937 gen(rd());
  89. return gen;
  90. }
  91. std::vector<Vec3f> its_sample_surface(const indexed_triangle_set &its,
  92. double sample_per_mm2,
  93. std::mt19937 random_generator = create_random_generator())
  94. {
  95. std::vector<Vec3f> samples;
  96. std::uniform_real_distribution<float> rand01(0.f, 1.f);
  97. for (const auto &triangle_indices : its.indices) {
  98. float area = triangle_area(triangle_indices, its.vertices);
  99. float countf;
  100. float fractional = std::modf(area * sample_per_mm2, &countf);
  101. int count = static_cast<int>(countf);
  102. float generate = rand01(random_generator);
  103. if (generate < fractional) ++count;
  104. if (count == 0) continue;
  105. const Vec3f &v0 = its.vertices[triangle_indices[0]];
  106. const Vec3f &v1 = its.vertices[triangle_indices[1]];
  107. const Vec3f &v2 = its.vertices[triangle_indices[2]];
  108. for (int c = 0; c < count; c++) {
  109. // barycentric coordinate
  110. Vec3f b;
  111. b[0] = rand01(random_generator);
  112. b[1] = rand01(random_generator);
  113. if ((b[0] + b[1]) > 1.f) {
  114. b[0] = 1.f - b[0];
  115. b[1] = 1.f - b[1];
  116. }
  117. b[2] = 1.f - b[0] - b[1];
  118. Vec3f pos;
  119. for (int i = 0; i < 3; i++) {
  120. pos[i] = b[0] * v0[i] + b[1] * v1[i] + b[2] * v2[i];
  121. }
  122. samples.push_back(pos);
  123. }
  124. }
  125. return samples;
  126. }
  127. #include "libslic3r/AABBTreeIndirect.hpp"
  128. // return Average abs distance to original
  129. float compare(const indexed_triangle_set &original,
  130. const indexed_triangle_set &simplified,
  131. double sample_per_mm2)
  132. {
  133. // create ABBTree
  134. auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
  135. original.vertices, original.indices);
  136. unsigned int init = 0;
  137. std::mt19937 rnd(init);
  138. auto samples = its_sample_surface(simplified, sample_per_mm2, rnd);
  139. float sumDistance = 0;
  140. for (const Vec3f &sample : samples) {
  141. size_t hit_idx;
  142. Vec3f hit_point;
  143. float distance2 = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
  144. original.vertices, original.indices, tree, sample, hit_idx,
  145. hit_point);
  146. sumDistance += sqrt(distance2);
  147. }
  148. return sumDistance / samples.size();
  149. }
  150. TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]")
  151. {
  152. indexed_triangle_set its;
  153. its.vertices = {Vec3f(-1.f, 0.f, 0.f), Vec3f(0.f, 1.f, 0.f),
  154. Vec3f(1.f, 0.f, 0.f), Vec3f(0.f, 0.f, 1.f),
  155. // vertex to be removed
  156. Vec3f(0.9f, .1f, -.1f)};
  157. its.indices = {Vec3i(1, 0, 3), Vec3i(2, 1, 3), Vec3i(0, 2, 3),
  158. Vec3i(0, 1, 4), Vec3i(1, 2, 4), Vec3i(2, 0, 4)};
  159. // edge to remove is between vertices 2 and 4 on trinagles 4 and 5
  160. indexed_triangle_set its_ = its; // copy
  161. // its_write_obj(its, "tetrhedron_in.obj");
  162. uint32_t wanted_count = its.indices.size() - 1;
  163. its_quadric_edge_collapse(its, wanted_count);
  164. // its_write_obj(its, "tetrhedron_out.obj");
  165. CHECK(its.indices.size() == 4);
  166. CHECK(its.vertices.size() == 4);
  167. for (size_t i = 0; i < 3; i++) {
  168. CHECK(its.indices[i] == its_.indices[i]);
  169. }
  170. for (size_t i = 0; i < 4; i++) {
  171. if (i == 2) continue;
  172. CHECK(its.vertices[i] == its_.vertices[i]);
  173. }
  174. const Vec3f &v = its.vertices[2]; // new vertex
  175. const Vec3f &v2 = its_.vertices[2]; // moved vertex
  176. const Vec3f &v4 = its_.vertices[4]; // removed vertex
  177. for (size_t i = 0; i < 3; i++) {
  178. bool is_between = (v[i] < v4[i] && v[i] > v2[i]) ||
  179. (v[i] > v4[i] && v[i] < v2[i]);
  180. CHECK(is_between);
  181. }
  182. float avg_distance = compare(its_, its, 10);
  183. CHECK(avg_distance < 8e-3f);
  184. }
  185. #include "test_utils.hpp"
  186. TEST_CASE("Simplify mesh by Quadric edge collapse to 5%", "[its]")
  187. {
  188. TriangleMesh mesh = load_model("frog_legs.obj");
  189. double original_volume = its_volume(mesh.its);
  190. uint32_t wanted_count = mesh.its.indices.size() * 0.05;
  191. REQUIRE_FALSE(mesh.empty());
  192. indexed_triangle_set its = mesh.its; // copy
  193. float max_error = std::numeric_limits<float>::max();
  194. its_quadric_edge_collapse(its, wanted_count, &max_error);
  195. //its_write_obj(its, "frog_legs_qec.obj");
  196. CHECK(its.indices.size() <= wanted_count);
  197. double volume = its_volume(its);
  198. CHECK(fabs(original_volume - volume) < 33.);
  199. float avg_distance = compare(mesh.its, its, 10);
  200. CHECK(avg_distance < 0.022f); // 0.02022 | 0.0199614074
  201. }