Corrected mesh split implementation

This commit is contained in:
tamasmeszaros 2021-05-28 14:55:25 +02:00
parent 8fdb0fddc0
commit c542e6e14b
6 changed files with 275 additions and 126 deletions

View file

@ -54,17 +54,10 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh,
{
openvdb::initialize();
std::vector<indexed_triangle_set> meshparts;
its_split(mesh, std::back_inserter(meshparts));
std::vector<indexed_triangle_set> meshparts = its_split(mesh);
// TriangleMeshPtrs meshparts_raw = mesh.split();
// auto meshparts = reserve_vector<std::unique_ptr<TriangleMesh>>(meshparts_raw.size());
// for (auto *p : meshparts_raw)
// meshparts.emplace_back(p);
auto it = std::remove_if(meshparts.begin(), meshparts.end(), [](auto &m) {
return its_volume(m) < EPSILON;
});
auto it = std::remove_if(meshparts.begin(), meshparts.end(),
[](auto &m) { return its_volume(m) < EPSILON; });
meshparts.erase(it, meshparts.end());

View file

@ -1183,45 +1183,132 @@ float its_volume(const indexed_triangle_set &its)
return volume;
}
PartMap::PartMap(const indexed_triangle_set & its,
const std::vector<std::vector<size_t>> &vfidx)
: count(0), face_part_indices(its.indices.size(), UNVISITED)
std::vector<size_t> its_find_unvisited_neighbors(
const indexed_triangle_set &its,
const FaceNeighborIndex & neighbor_index,
std::vector<bool> & visited)
{
auto next_face_idx = [this](size_t start) {
size_t i = start;
while (face_part_indices[i++] >= 0);
return i;
using stack_el = size_t;
auto facestack = reserve_vector<stack_el>(its.indices.size());
auto push = [&facestack] (const stack_el &s) { facestack.emplace_back(s); };
auto pop = [&facestack] () -> stack_el {
stack_el ret = facestack.back();
facestack.pop_back();
return ret;
};
size_t face_idx = 0;
size_t part_idx = 0;
// find the next unvisited facet and push the index
auto facet = std::find(visited.begin(), visited.end(), false);
std::vector<size_t> ret;
do {
face_idx = next_face_idx(face_idx);
} while(split_recurse(its, vfidx, face_idx, part_idx++));
count = size_t(part_idx - 1);
}
bool PartMap::split_recurse(const indexed_triangle_set & its,
const std::vector<std::vector<size_t>> &vfidx,
size_t fi,
size_t part_idx)
{
if (face_part_indices[fi] >= 0)
return false;
face_part_indices[fi] = part_idx;
const auto &face = its.indices[fi];
for (size_t v = 0; v < 3; ++v) {
auto vi = face(v);
const std::vector<size_t> neigh_faces = vfidx[vi];
for (size_t neigh_face : neigh_faces)
split_recurse(its, vfidx, neigh_face, part_idx);
if (facet != visited.end()) {
ret.reserve(its.indices.size());
auto idx = size_t(facet - visited.begin());
push(idx);
ret.emplace_back(idx);
visited[idx] = true;
}
return true;
while (!facestack.empty()) {
size_t facet_idx = pop();
const auto &neighbors = neighbor_index[facet_idx];
for (size_t neighbor_idx : neighbors) {
if (!visited[neighbor_idx]) {
visited[neighbor_idx] = true;
push(neighbor_idx);
ret.emplace_back(neighbor_idx);
}
}
}
return ret;
}
bool its_is_splittable(const indexed_triangle_set &its,
const FaceNeighborIndex & neighbor_index)
{
std::vector<bool> visited(its.indices.size(), false);
its_find_unvisited_neighbors(its, neighbor_index, visited);
// Try finding an unvisited facet. If there are none, the mesh is not splittable.
auto it = std::find(visited.begin(), visited.end(), false);
return it != visited.end();
}
std::vector<indexed_triangle_set> its_split(
const indexed_triangle_set &its, const FaceNeighborIndex &neighbor_index)
{
auto ret = reserve_vector<indexed_triangle_set>(3);
its_split(its, std::back_inserter(ret), neighbor_index);
return ret;
}
FaceNeighborIndex its_create_neighbors_index(const indexed_triangle_set &its)
{
// Just to be clear what type of object are we referencing
using FaceID = size_t;
using VertexID = uint64_t;
using EdgeID = uint64_t;
constexpr auto UNASSIGNED = std::numeric_limits<FaceID>::max();
struct Edge // Will contain IDs of the two facets touching this edge
{
FaceID first, second;
Edge() : first{UNASSIGNED}, second{UNASSIGNED} {}
void assign(FaceID fid)
{
first == UNASSIGNED ? first = fid : second = fid;
}
};
// All vertex IDs will fit into this number of bits. (Used for hashing)
const int max_vertex_id_bits = std::ceil(std::log2(its.vertices.size()));
assert(max_vertex_id_bits <= 32);
std::unordered_map< EdgeID, Edge > edge_index;
// Edge id is constructed by concatenating two vertex ids, starting with
// the lowest in MSB
auto hash = [max_vertex_id_bits] (VertexID a, VertexID b) {
if (a > b) std::swap(a, b);
return (a << max_vertex_id_bits) + b;
};
// Go through all edges of all facets and mark the facets touching each edge
for (size_t face_id = 0; face_id < its.indices.size(); ++face_id) {
const Vec3i &face = its.indices[face_id];
EdgeID e1 = hash(face(0), face(1)), e2 = hash(face(1), face(2)),
e3 = hash(face(2), face(0));
edge_index[e1].assign(face_id);
edge_index[e2].assign(face_id);
edge_index[e3].assign(face_id);
}
FaceNeighborIndex index(its.indices.size());
// Now collect the neighbors for each facet into the final index
for (size_t face_id = 0; face_id < its.indices.size(); ++face_id) {
const Vec3i &face = its.indices[face_id];
EdgeID e1 = hash(face(0), face(1)), e2 = hash(face(1), face(2)),
e3 = hash(face(2), face(0));
const Edge &neighs1 = edge_index[e1];
const Edge &neighs2 = edge_index[e2];
const Edge &neighs3 = edge_index[e3];
std::array<size_t, 3> &neighs = index[face_id];
neighs[0] = neighs1.first == face_id ? neighs1.second : neighs1.first;
neighs[1] = neighs2.first == face_id ? neighs2.second : neighs2.first;
neighs[2] = neighs3.first == face_id ? neighs3.second : neighs3.first;
}
return index;
}
} // namespace Slic3r

View file

@ -136,77 +136,74 @@ int its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit =
// Remove vertices, which none of the faces references. Return number of freed vertices.
int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit = true);
// Used by its_split to map each face of a mesh to a part index. Can be used
// to query the number of parts in a mesh.
struct PartMap
{
static constexpr int UNVISITED = -1;
using FaceNeighborIndex = std::vector< std::array<size_t, 3> >;
size_t count;
std::vector<int> face_part_indices;
// Create index that gives neighbor faces for each face. Ignores face orientations.
FaceNeighborIndex its_create_neighbors_index(const indexed_triangle_set &its);
PartMap(const indexed_triangle_set & its,
const std::vector<std::vector<size_t>> &vfidx);
explicit PartMap(const indexed_triangle_set &its)
: PartMap(its, create_vertex_faces_index(its))
{}
private:
bool split_recurse(const indexed_triangle_set & its,
const std::vector<std::vector<size_t>> &vfidx,
size_t fi,
size_t part_idx);
};
template<class OutputIt>
void its_split(const indexed_triangle_set &its,
const PartMap & partmap,
OutputIt out_it)
{
std::vector<indexed_triangle_set> meshes(partmap.count);
std::vector<int> vidx_conv(its.vertices.size() * meshes.size(),
PartMap::UNVISITED);
auto &parts = partmap.face_part_indices;
for (size_t fi = 0; fi < parts.size(); ++fi) {
int pi = parts[fi];
if (pi < 0) continue;
indexed_triangle_set &part_its = meshes[size_t(pi)];
const auto & face = its.indices[fi];
size_t conv_begin = (pi * its.vertices.size());
Vec3i new_face;
for (size_t v = 0; v < 3; ++v) {
auto vi = face(v);
size_t conv_idx = conv_begin + vi;
if (vidx_conv[conv_idx] == PartMap::UNVISITED) {
vidx_conv[conv_idx] = part_its.vertices.size();
part_its.vertices.emplace_back(its.vertices[size_t(vi)]);
}
new_face(v) = vidx_conv[conv_idx];
}
part_its.indices.emplace_back(new_face);
}
for (indexed_triangle_set &part_its : meshes)
out_it = std::move(part_its);
}
// Visit all unvisited neighboring facets that are reachable from the first unvisited facet,
// and return them.
std::vector<size_t> its_find_unvisited_neighbors(
const indexed_triangle_set &its,
const FaceNeighborIndex & neighbor_index,
std::vector<bool> & visited);
// Splits a mesh into multiple meshes when possible.
template<class OutputIt>
void its_split(const indexed_triangle_set & its,
OutputIt out_it)
OutputIt out_it,
const FaceNeighborIndex &neighbor_index_ = {})
{
its_split(its, PartMap{its}, out_it);
const auto &neighbor_index = neighbor_index_.empty() ?
its_create_neighbors_index(its) :
neighbor_index_;
std::vector<bool> visited(its.indices.size(), false);
const size_t UNASSIGNED = its.vertices.size();
std::vector<size_t> vidx_conv(its.vertices.size());
for (;;) {
std::vector<size_t> facets =
its_find_unvisited_neighbors(its, neighbor_index, visited);
if (facets.empty())
break;
std::fill(vidx_conv.begin(), vidx_conv.end(), UNASSIGNED);
// Create a new mesh for the part that was just split off.
indexed_triangle_set mesh;
// Assign the facets to the new mesh.
for (size_t face_id : facets) {
const auto &face = its.indices[face_id];
Vec3i new_face;
for (size_t v = 0; v < 3; ++v) {
auto vi = face(v);
if (vidx_conv[vi] == UNASSIGNED) {
vidx_conv[vi] = mesh.vertices.size();
mesh.vertices.emplace_back(its.vertices[size_t(vi)]);
}
new_face(v) = vidx_conv[vi];
}
mesh.indices.emplace_back(new_face);
}
out_it = std::move(mesh);
}
}
std::vector<indexed_triangle_set> its_split(
const indexed_triangle_set &its,
const FaceNeighborIndex & neighbor_index = {});
bool its_is_splittable(const indexed_triangle_set &its,
const FaceNeighborIndex & neighbor_index = {});
// Shrink the vectors of its.vertices and its.faces to a minimum size by reallocating the two vectors.
void its_shrink_to_fit(indexed_triangle_set &its);

View file

@ -21,6 +21,7 @@ add_executable(${_TEST_NAME}_tests
test_optimizers.cpp
test_png_io.cpp
test_timeutils.cpp
test_indexed_triangle_set.cpp
)
if (TARGET OpenVDB::openvdb)

View file

@ -20,23 +20,3 @@ TEST_CASE("Hollow two overlapping spheres") {
sphere1.WriteOBJFile("twospheres.obj");
}
TEST_CASE("Split its") {
using namespace Slic3r;
TriangleMesh sphere1 = make_sphere(10., 2 * PI / 20.), sphere2 = sphere1;
sphere1.translate(-5.f, 0.f, 0.f);
sphere2.translate( 5.f, 0.f, 0.f);
sphere1.merge(sphere2);
sphere1.require_shared_vertices();
std::vector<indexed_triangle_set> parts;
its_split(sphere1.its, std::back_inserter(parts));
size_t part_idx = 0;
for (auto &part_its : parts) {
its_write_obj(part_its, (std::string("part_its") + std::to_string(part_idx++) + ".obj").c_str());
}
}

View file

@ -0,0 +1,91 @@
#include <iostream>
#include <fstream>
#include <catch2/catch.hpp>
#include "libslic3r/TriangleMesh.hpp"
//#include "libnest2d/tools/benchmark.h"
TEST_CASE("Split empty mesh", "[its_split][its]") {
using namespace Slic3r;
indexed_triangle_set its;
std::vector<indexed_triangle_set> res;
its_split(its, std::back_inserter(res));
REQUIRE(res.empty());
}
TEST_CASE("Split simple mesh consisting of one part", "[its_split][its]") {
using namespace Slic3r;
TriangleMesh cube = make_cube(10., 10., 10.);
std::vector<indexed_triangle_set> res;
its_split(cube.its, std::back_inserter(res));
REQUIRE(res.size() == 1);
REQUIRE(res.front().indices.size() == cube.its.indices.size());
REQUIRE(res.front().vertices.size() == cube.its.vertices.size());
}
TEST_CASE("Split two merged spheres", "[its_split][its]") {
using namespace Slic3r;
TriangleMesh sphere1 = make_sphere(10., 2 * PI / 200.), sphere2 = sphere1;
sphere1.translate(-5.f, 0.f, 0.f);
sphere2.translate( 5.f, 0.f, 0.f);
sphere1.merge(sphere2);
sphere1.require_shared_vertices();
// Benchmark bench;
// bench.start();
auto index = its_create_neighbors_index(sphere1.its);
std::vector<indexed_triangle_set> parts = its_split(sphere1.its, index);
// bench.stop();
// std::cout << "split took " << bench.getElapsedSec() << " seconds." << std::endl;
REQUIRE(parts.size() == 2);
#ifndef NDEBUG
size_t part_idx = 0;
for (auto &part_its : parts) {
its_write_obj(part_its, (std::string("part_its") + std::to_string(part_idx++) + ".obj").c_str());
}
#endif
}
//TEST_CASE("Split two merged spheres TriangleMesh", "[its_split][its]") {
// using namespace Slic3r;
// TriangleMesh sphere1 = make_sphere(10., 2 * PI / 200.), sphere2 = sphere1;
// sphere1.translate(-5.f, 0.f, 0.f);
// sphere2.translate( 5.f, 0.f, 0.f);
// sphere1.merge(sphere2);
// sphere1.require_shared_vertices();
// Benchmark bench;
// bench.start();
// TriangleMeshPtrs parts = sphere1.split();
// for (auto &part : parts) part->require_shared_vertices();
// bench.stop();
// std::cout << "split took " << bench.getElapsedSec() << " seconds." << std::endl;
// REQUIRE(parts.size() == 2);
////#ifndef NDEBUG
//// size_t part_idx = 0;
//// for (auto &part : parts) {
//// its_write_obj(part->its, (std::string("part_its") + std::to_string(part_idx++) + ".obj").c_str());
//// }
////#endif
//}