Corrected mesh split implementation
This commit is contained in:
parent
8fdb0fddc0
commit
c542e6e14b
6 changed files with 275 additions and 126 deletions
|
@ -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());
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
91
tests/libslic3r/test_indexed_triangle_set.cpp
Normal file
91
tests/libslic3r/test_indexed_triangle_set.cpp
Normal 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
|
||||
//}
|
Loading…
Reference in a new issue