218 lines
6.1 KiB
C++
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
|