TU-Programmieren_2/exercise5/TriangleMesh.cpp
2025-04-09 10:22:44 +02:00

218 lines
6.1 KiB
C++

/// @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 <algorithm> // std::sort, std::ranges::max, std::max
#include <cassert> // assert
#include <filesystem> // std::filesystem
#include <iostream> // std::cout, std::endl
#include <limits> // std::numeric_limits
#include <map> // std::map
#include <set> // std::set, std::multiset
#include <tuple> // std::tuple
#include <unordered_set> // std::unordered_set
#include <utility> // std::pair
#include <vector> // 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<double>::max(), +std::numeric_limits<double>::max()};
Vec2d bbmax = {-std::numeric_limits<double>::max(), -std::numeric_limits<double>::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::Vec2d>& TriangleMesh::getVertices() const { return vertices; }
const std::vector<TriangleMesh::Vec3i>& 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<iue::svg::Triangle> 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<size_t> vertices_in_use = {};
std::multiset<std::pair<size_t, size_t>> 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<size_t>{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<size_t, size_t> TriangleMesh::remove_vertices(std::unordered_set<size_t> 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<size_t> 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<size_t, ptrdiff_t> offsets;
ptrdiff_t offset = 0;
size_t index = 0;
auto remove = [&index, &indices, &offsets, &offset](const Vec2d&) -> bool {
if (indices.contains(index)) {
std::pair<size_t, ptrdiff_t> pair = {std::max<ptrdiff_t>(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<size_t, ptrdiff_t> pair = {std::max<ptrdiff_t>(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