|
| 1 | +#include <Eigen/Core> |
| 2 | +#include <Eigen/Geometry> |
| 3 | +#include <catch2/catch_amalgamated.hpp> |
| 4 | +#include <fstream> |
| 5 | +#include <random> |
| 6 | +#include <spdlog/spdlog.h> |
| 7 | + |
| 8 | +#include "../src/geometry/halfedge.h" |
| 9 | +#include "../src/scene/group.h" |
| 10 | +#include "../src/scene/object.h" |
| 11 | +#include "../src/utils/formatter.hpp" |
| 12 | +#include "../src/utils/math.hpp" |
| 13 | + |
| 14 | +using Eigen::AngleAxisf; |
| 15 | +using Eigen::Matrix4f; |
| 16 | +using Eigen::Scaling; |
| 17 | +using Eigen::Translation3f; |
| 18 | +using Eigen::Vector3f; |
| 19 | +using Eigen::Vector4f; |
| 20 | +using std::default_random_engine; |
| 21 | +using std::random_device; |
| 22 | +using std::uniform_real_distribution; |
| 23 | + |
| 24 | +using std::pair; |
| 25 | +using std::set; |
| 26 | +using std::string; |
| 27 | +using std::unordered_map; |
| 28 | +using std::vector; |
| 29 | + |
| 30 | +constexpr float threshold = 1e-3f; |
| 31 | +constexpr float threshold_squ = 1e-6f; |
| 32 | + |
| 33 | +TEST_CASE("Loop Subdivision", "[geometry]") |
| 34 | +{ |
| 35 | + |
| 36 | + vector<pair<string, string>> test_cases = { |
| 37 | + {"../input/geometry/cube.obj", "../ans/geometry/loop_subdivision/cube.txt"}, |
| 38 | + {"../input/geometry/sphere.obj", "../ans/geometry/loop_subdivision/sphere.txt"}, |
| 39 | + {"../input/geometry/cow.dae", "../ans/geometry/loop_subdivision/cow.txt"}, |
| 40 | + {"../input/geometry/teapot.dae", "../ans/geometry/loop_subdivision/teapot.txt"}, |
| 41 | + {"../input/geometry/bunny.obj", "../ans/geometry/loop_subdivision/bunny.txt"}}; |
| 42 | + |
| 43 | + size_t case_id = 0; |
| 44 | + for (const auto& test_case : test_cases) { |
| 45 | + auto model_path = test_case.first; |
| 46 | + auto std_result_path = test_case.second; |
| 47 | + |
| 48 | + spdlog::info("Case #{}: Testing loop subdivision of: {}", ++case_id, model_path); |
| 49 | + |
| 50 | + // Step 1. Load Test and STD Data |
| 51 | + Group test_group("Test group"); |
| 52 | + bool model_load_ok = test_group.load(model_path); |
| 53 | + REQUIRE(model_load_ok); |
| 54 | + REQUIRE(!test_group.objects.empty()); |
| 55 | + |
| 56 | + HalfedgeMesh test_mesh(**test_group.objects.begin()); |
| 57 | + test_mesh.loop_subdivide(); |
| 58 | + |
| 59 | + std::ifstream std_result_file(std_result_path); |
| 60 | + REQUIRE(std_result_file.is_open()); |
| 61 | + |
| 62 | + size_t std_vertex_count; |
| 63 | + std_result_file >> std_vertex_count; |
| 64 | + vector<Vector3f> std_vertices(std_vertex_count); |
| 65 | + |
| 66 | + for (size_t i = 0; i < std_vertex_count; ++i) { |
| 67 | + float x, y, z; |
| 68 | + std_result_file >> x >> y >> z; |
| 69 | + std_vertices[i] = Vector3f(x, y, z); |
| 70 | + } |
| 71 | + |
| 72 | + size_t std_edge_count; |
| 73 | + std_result_file >> std_edge_count; |
| 74 | + set<pair<size_t, size_t>> std_edges; |
| 75 | + |
| 76 | + for (size_t i = 0; i < std_edge_count; ++i) { |
| 77 | + size_t v1, v2; |
| 78 | + std_result_file >> v1 >> v2; |
| 79 | + std_edges.insert({v1, v2}); |
| 80 | + } |
| 81 | + |
| 82 | + std_result_file.close(); |
| 83 | + |
| 84 | + // Step 2. Point Check and Mapping |
| 85 | + size_t test_vertex_count = test_mesh.vertices.size; |
| 86 | + INFO("Vertex count: Test: " << test_vertex_count << ", Expected: " << std_vertex_count); |
| 87 | + REQUIRE(test_vertex_count == std_vertex_count); |
| 88 | + |
| 89 | + unordered_map<Vertex*, size_t> test_vertex_id; |
| 90 | + |
| 91 | + for (Vertex* v = test_mesh.vertices.head; v != nullptr; v = v->next_node) { |
| 92 | + std::optional<size_t> id = std::nullopt; |
| 93 | + for (size_t i = 0; i < test_vertex_count; i++) { |
| 94 | + if ((v->pos - std_vertices[i]).squaredNorm() < threshold_squ) { |
| 95 | + id = i; |
| 96 | + break; |
| 97 | + } |
| 98 | + } |
| 99 | + INFO("At least one vertex has wrong position."); |
| 100 | + REQUIRE(id.has_value()); |
| 101 | + test_vertex_id[v] = id.value(); |
| 102 | + } |
| 103 | + |
| 104 | + // Step 3. Edge Check |
| 105 | + size_t test_edge_count = test_mesh.edges.size; |
| 106 | + INFO("Edge count: Test: " << test_edge_count << ", Expected: " << std_edge_count); |
| 107 | + REQUIRE(test_edge_count == std_edge_count); |
| 108 | + |
| 109 | + for (Edge* e = test_mesh.edges.head; e != nullptr; e = e->next_node) { |
| 110 | + Vertex* v1 = e->halfedge->from; |
| 111 | + Vertex* v2 = e->halfedge->inv->from; |
| 112 | + size_t id1 = test_vertex_id[v1]; |
| 113 | + size_t id2 = test_vertex_id[v2]; |
| 114 | + |
| 115 | + auto edge_pair = std::make_pair(id1, id2); |
| 116 | + auto edge_pair_reversed = std::make_pair(id2, id1); |
| 117 | + |
| 118 | + auto it = std_edges.find(edge_pair); |
| 119 | + if (it == std_edges.end()) { |
| 120 | + it = std_edges.find(edge_pair_reversed); |
| 121 | + } |
| 122 | + INFO("At least one edge connects wrong vertexs."); |
| 123 | + REQUIRE(it != std_edges.end()); |
| 124 | + } |
| 125 | + spdlog::info("Test Pass: loop subdivision of: {}", model_path); |
| 126 | + } |
| 127 | +} |
0 commit comments