/// @file /// @brief TriangleMesh: member function implementation/definitions #include "TriangleMesh.hpp" // ex5::TriangleMesh #include "iue-svg/render.hpp" // iue::svg::render #include "iue-svg/tags.hpp" // iue::svg::Triangle #include // std::sort, std::ranges::max, std::max #include // assert #include // std::filesystem #include // std::cout, std::endl #include // std::numeric_limits #include // std::map #include // std::set, std::multiset #include // std::tuple #include // std::unordered_set #include // std::pair #include // std::vector, std::erase_if namespace ex5 { TriangleMesh::TriangleMesh(TriangleMesh::BBox box, double h) : vertices(), triangles() { auto [min, max] = box; assert(max[0] > min[0]); assert(max[1] > min[1]); auto width = max[0] - min[0]; auto height = max[1] - min[1]; int nx = (width / h) + 1; int ny = (height / h) + 1; assert(nx >= 2); assert(ny >= 2); vertices.resize(nx * ny); for (size_t iy = 0; iy != ny; ++iy) { for (size_t ix = 0; ix != nx; ++ix) { vertices.at(ix + iy * nx) = {ix * h + min[0], iy * h + min[1]}; } } triangles.reserve((nx - 1) * (ny - 1) * 2); for (size_t iy = 1; iy != ny; ++iy) { for (size_t ix = 1; ix != nx; ++ix) { auto i = ix + iy * nx; auto ia = i - nx - 1; auto ib = i - nx; auto ic = i; auto id = i - 1; triangles.push_back({ia, ib, id}); triangles.push_back({ib, ic, id}); } } } TriangleMesh::BBox TriangleMesh::bbox() const { Vec2d bbmin = {+std::numeric_limits::max(), +std::numeric_limits::max()}; Vec2d bbmax = {-std::numeric_limits::max(), -std::numeric_limits::max()}; // update minmax from single coordinate auto minmax = [&bbmin, &bbmax](const Vec2d& p) { bbmin[0] = bbmin[0] < p[0] ? bbmin[0] : p[0]; bbmin[1] = bbmin[1] < p[1] ? bbmin[1] : p[1]; bbmax[0] = bbmax[0] > p[0] ? bbmax[0] : p[0]; bbmax[1] = bbmax[1] > p[1] ? bbmax[1] : p[1]; }; for (const auto& v : vertices) { minmax(v); } return {bbmin, bbmax}; } const std::vector& TriangleMesh::getVertices() const { return vertices; } const std::vector& TriangleMesh::getTriangles() const { return triangles; } void TriangleMesh::print() const { std::cout << "Mesh [" << std::endl; std::cout << " vertices [" << std::endl; { // print vertices size_t index = 0; for (const auto& [x, y] : vertices) { std::cout << " " << index++ << ": (" << x << ", " << y << " )" << std::endl; } std::cout << " ]" << std::endl; } { // print triangles size_t index = 0; std::cout << " triangles [" << std::endl; for (const auto& [ia, ib, ic] : triangles) { std::cout << " " << index++ << ": (" << ia << ", " << ib << ", " << ic << " )" << std::endl; } std::cout << " ]" << std::endl; } std::cout << "]" << std::endl; } TriangleMesh::BBox TriangleMesh::save(std::filesystem::path filepath) const { std::vector tris; for (const auto& [ia, ib, ic] : triangles) { tris.push_back({vertices[ia], vertices[ib], vertices[ic]}); } return iue::svg::render(filepath, {}, {}, tris); } bool TriangleMesh::check_invariants() const { std::set vertices_in_use = {}; std::multiset> edges; for (auto i3 : triangles) { std::sort(i3.begin(), i3.end()); edges.insert({i3[0], i3[1]}); edges.insert({i3[1], i3[2]}); edges.insert({i3[2], i3[0]}); // check: each triangle references three distinct corners if (std::set{i3[0], i3[1], i3[2]}.size() != 3) { return false; } // check: each triangle references valid indices if (std::ranges::max(i3) >= vertices.size()) { return false; } vertices_in_use.insert(i3[0]); vertices_in_use.insert(i3[1]); vertices_in_use.insert(i3[2]); } // check: edges are not occupied more than twice (no hidden edges) for (const auto& edge : edges) { if (edges.count(edge) > 2) { return false; } } // check: no unreferenced vertices are present if (vertices_in_use.size() != vertices.size()) { return false; } return true; } std::tuple TriangleMesh::remove_vertices(std::unordered_set indices) { if (indices.empty()) return {0, 0}; assert(indices.size() != 0); assert(std::ranges::max(indices) <= vertices.size()); // remove connected triangles auto connected = [&indices](const Vec3i& triangle) -> bool { const auto& [ia, ib, ic] = triangle; return indices.contains(ia) || indices.contains(ib) || indices.contains(ic); }; auto num_removed_triangles = std::erase_if(triangles, connected); // find additional points which became unused (due to removal of triangles) and ... std::unordered_set in_use; for (auto& [ia, ib, ic] : triangles) { in_use.insert(ia); in_use.insert(ib); in_use.insert(ic); } // .. add those to the list of indices to be removed for (size_t i = 0; i != vertices.size(); ++i) { if (in_use.find(i) == in_use.end()) { indices.insert(i); } } // remove vertices and record offsets std::map offsets; ptrdiff_t offset = 0; size_t index = 0; auto remove = [&index, &indices, &offsets, &offset](const Vec2d&) -> bool { if (indices.contains(index)) { std::pair pair = {std::max(index - 1, 0), offset--}; offsets.insert(pair); ++index; return true; } ++index; return false; }; auto num_removed_vertices = std::erase_if(vertices, remove); // add final offset std::pair pair = {std::max(index - 1, 0), offset}; offsets.insert(pair); // update triangle indices using recorded offsets for (auto& triangle : triangles) { for (auto& i : triangle) { auto iter = offsets.lower_bound(i); if (iter != offsets.end()) i += iter->second; } } return {num_removed_vertices, num_removed_triangles}; } } // namespace ex5