diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index 69b649f5c..72c7668a4 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -54,17 +54,10 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh, { openvdb::initialize(); - std::vector meshparts; - its_split(mesh, std::back_inserter(meshparts)); + std::vector meshparts = its_split(mesh); -// TriangleMeshPtrs meshparts_raw = mesh.split(); -// auto meshparts = reserve_vector>(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()); diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index b29771357..6f82830ea 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1183,45 +1183,132 @@ float its_volume(const indexed_triangle_set &its) return volume; } -PartMap::PartMap(const indexed_triangle_set & its, - const std::vector> &vfidx) - : count(0), face_part_indices(its.indices.size(), UNVISITED) +std::vector its_find_unvisited_neighbors( + const indexed_triangle_set &its, + const FaceNeighborIndex & neighbor_index, + std::vector & 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(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 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> &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 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 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 its_split( + const indexed_triangle_set &its, const FaceNeighborIndex &neighbor_index) +{ + auto ret = reserve_vector(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::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 &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 diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index f31e4dcef..3439eda4e 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -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 count; - std::vector 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> &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> &vfidx, - size_t fi, - size_t part_idx); -}; - -template -void its_split(const indexed_triangle_set &its, - const PartMap & partmap, - OutputIt out_it) -{ - std::vector meshes(partmap.count); - - std::vector 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 its_find_unvisited_neighbors( + const indexed_triangle_set &its, + const FaceNeighborIndex & neighbor_index, + std::vector & visited); +// Splits a mesh into multiple meshes when possible. template 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 visited(its.indices.size(), false); + + const size_t UNASSIGNED = its.vertices.size(); + std::vector vidx_conv(its.vertices.size()); + + for (;;) { + std::vector 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 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); diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index f93cf10af..5a4203ea0 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -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) diff --git a/tests/libslic3r/test_hollowing.cpp b/tests/libslic3r/test_hollowing.cpp index 5662d8b0d..1f5ca3845 100644 --- a/tests/libslic3r/test_hollowing.cpp +++ b/tests/libslic3r/test_hollowing.cpp @@ -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 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()); - } -} - diff --git a/tests/libslic3r/test_indexed_triangle_set.cpp b/tests/libslic3r/test_indexed_triangle_set.cpp new file mode 100644 index 000000000..ae493169a --- /dev/null +++ b/tests/libslic3r/test_indexed_triangle_set.cpp @@ -0,0 +1,91 @@ +#include +#include +#include + +#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 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 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 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 +//}