From 31610ab39db018c7dad5ed74f0587d968ea59a28 Mon Sep 17 00:00:00 2001 From: Jirawat I Date: Sat, 14 Mar 2020 03:21:51 +0700 Subject: [PATCH] add new program to fix self-intersect mesh --- CMakeLists.txt | 4 + include/Mesh.h | 271 +++++++++++++++++++++++++++++++++--- include/OptimalSlice.hpp | 30 +++- include/ProgressBar.hpp | 2 + include/Scaffolder_2.h | 156 +++------------------ include/implicit_function.h | 6 - include/utils.h | 10 +- src/FixSelfIntersect.cpp | 142 +++++++++++++++++++ src/Main.cpp | 16 ++- 9 files changed, 462 insertions(+), 175 deletions(-) create mode 100644 src/FixSelfIntersect.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e2acbd..a659c48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,3 +44,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE igl::core DIP tbb_static) add_executable(SliceTest ${MAIN_SOURCES} ${PROJECT_SOURCE_DIR}/src/SliceTest.cpp) target_include_directories(SliceTest PRIVATE "${PROJECT_SOURCE_DIR}/include" ${TBB_INCLUDE_DIR} ${VCG_INCLUDE_DIR}) target_link_libraries(SliceTest PRIVATE igl::core tbb_static) + +add_executable(Fixer ${MAIN_SOURCES} ${PROJECT_SOURCE_DIR}/src/FixSelfIntersect.cpp ${VCG_INCLUDE_DIR}/wrap/ply/plylib.cpp) +target_include_directories(Fixer PRIVATE "${PROJECT_SOURCE_DIR}/include" ${TBB_INCLUDE_DIR} ${VCG_INCLUDE_DIR}) +target_link_libraries(Fixer PRIVATE) diff --git a/include/Mesh.h b/include/Mesh.h index ab1b9bb..b13adde 100644 --- a/include/Mesh.h +++ b/include/Mesh.h @@ -1,12 +1,13 @@ -#ifndef SCAFFOLD_TMESH_H -#define SCAFFOLD_TMESH_H +#pragma once #include #include #include #include #include #include -#include +#ifndef SLICE_PRECISION +#define SLICE_PRECISION 1e-8 +#endif class TFace; class TVertex; @@ -30,26 +31,252 @@ class TFace : public vcg::Face, vector > {}; -inline void mesh_to_eigen_vector(TMesh& mesh, Eigen::MatrixXd& V, Eigen::MatrixXi& F) { - V.resize(mesh.VN(), 3); - size_t i = 0; - std::vector vertexId(mesh.vert.size()); - for (TMesh::VertexIterator it = mesh.vert.begin(); it != mesh.vert.end(); ++it) if (!it->IsD()) { - vertexId[it - mesh.vert.begin()] = i; - vcg::Point3d point = it->P(); - V(i, 0) = point[0]; - V(i, 1) = point[1]; - V(i, 2) = point[2]; - i++; +inline void clean_mesh(TMesh& mesh, double minimum_diameter, uint16_t smooth_step, bool verbose = true) { + if (verbose) std::cout << "[libVCG Cleaning] "; + vcg::tri::Clean::RemoveDuplicateVertex(mesh); + vcg::tri::Allocator::CompactEveryVector(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + vcg::tri::Clean::RemoveDuplicateFace(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + vcg::tri::Clean::RemoveZeroAreaFace(mesh); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + vcg::tri::UpdateBounding::Box(mesh); + vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + vcg::tri::UpdateBounding::Box(mesh); + if (verbose) std::cout << "OK" << std::endl; + if (smooth_step > 0) { + if (verbose) std::cout << "[Laplacian smoothing] "; + vcg::tri::Smooth::VertexCoordLaplacian(mesh, smooth_step, false, true); + if (verbose) std::cout << "OK" << std::endl; } - // Faces to Eigen matrixXi F1 - i = 0; - F.resize(mesh.FN(), mesh.face.begin()->VN()); - for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); ++it) if (!it->IsD()) { - for (int k = 0; k < it->VN(); k++) { - F(i, k) = vertexId[vcg::tri::Index(mesh, it->V(k))]; + vcg::tri::Clean::MergeCloseVertex(mesh, SLICE_PRECISION * 1000); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + vcg::tri::Allocator::CompactEveryVector(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); +} + +inline bool fix_non_manifold(TMesh& mesh, double minimum_diameter, uint16_t max_iteration = 5, bool verbose = false) { + size_t nf = 1; + int maxSize = mesh.bbox.SquaredDiag(); + + int edgeNum = 0, edgeBorderNum = 0, edgeNonManifoldNum = 0; + vcg::tri::Clean::CountEdgeNum(mesh, edgeNum, edgeBorderNum, edgeNonManifoldNum); + + for (uint16_t iteration = 0; (edgeBorderNum > 0 || edgeNonManifoldNum > 0) && iteration < max_iteration; iteration++) { + if (verbose) std::cout << "[Fix non-manifold]" << std::endl << " -- Iteration " << iteration + 1 << std::endl; + if (verbose) std::cout << " -- non-manifold edges: " << edgeNonManifoldNum << std::endl; + if (edgeNonManifoldNum > 0) { + // fix floating faces + vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + if (verbose) std::cout << " -- Remove small components " << minimum_diameter * mesh.bbox.Diag() << " [OK]" << std::endl; + // fix non-manifold edges + vcg::tri::Clean::RemoveNonManifoldFace(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove non-manifold edges [OK]" << std::endl; + // fix holes + if (vcg::tri::Clean::CountNonManifoldEdgeFF(mesh) > 0) { + std::cout << "[Warning]: Fixed Self-intersecting failed: Mesh has some not 2-manifold edges" << std::endl; + return false; + } + vcg::tri::Hole::EarCuttingIntersectionFill>(mesh, maxSize, false); + if (verbose) std::cout << " -- Close holes [OK]" << std::endl; + } + if (verbose) std::cout << " -- border edges: " << edgeBorderNum << std::endl; + if (edgeBorderNum > 0) { + // select border vertices and faces + vcg::tri::UpdateFlags::FaceBorderFromNone(mesh); + vcg::tri::UpdateFlags::VertexBorderFromFaceBorder(mesh); + vcg::tri::UpdateSelection::FaceFromBorderFlag(mesh); + vcg::tri::UpdateSelection::VertexFromBorderFlag(mesh); + // Dilate selection + vcg::tri::UpdateSelection::VertexFromFaceLoose(mesh); + vcg::tri::UpdateSelection::FaceFromVertexLoose(mesh); + vcg::tri::UpdateSelection::VertexClear(mesh); + vcg::tri::UpdateSelection::VertexFromFaceStrict(mesh); + // delete all selected + for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); ++it) { + if (!it->IsD() && it->IsS()) + vcg::tri::Allocator::DeleteFace(mesh, *it); + } + for (TMesh::VertexIterator it = mesh.vert.begin(); it != mesh.vert.end(); ++it) { + if (!it->IsD() && it->IsS()) + vcg::tri::Allocator::DeleteVertex(mesh, *it); + } + vcg::tri::Allocator::CompactEveryVector(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove Border faces [OK]" << std::endl; + // fix floating faces + vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + if (verbose) std::cout << " -- Remove small components " << minimum_diameter * mesh.bbox.Diag() << " [OK]" << std::endl; + // fix non-manifold edges + vcg::tri::Clean::RemoveNonManifoldFace(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove non-manifold edges [OK]" << std::endl; + // fix holes + if (vcg::tri::Clean::CountNonManifoldEdgeFF(mesh) > 0) { + std::cout << "[Warning]: Fixed Self-intersecting failed: Mesh has some not 2-manifold edges" << std::endl; + return false; + } + vcg::tri::Hole::EarCuttingIntersectionFill>(mesh, maxSize, false); + if (verbose) std::cout << " -- Close holes [OK]" << std::endl; + } + + vcg::tri::Clean::CountEdgeNum(mesh, edgeNum, edgeBorderNum, edgeNonManifoldNum); + } + + if (edgeBorderNum > 0 || edgeNonManifoldNum > 0) return false; + return true; +} + +inline bool fix_self_intersect_mesh(TMesh& mesh, double minimum_diameter, uint16_t max_iteration = 10, bool verbose = false) { + + std::vector faces; + std::vector::iterator fc; + vcg::tri::Clean::SelfIntersections(mesh, faces); + + uint16_t iteration = 0; + int maxSize = mesh.bbox.SquaredDiag(); + size_t nf = 0, nv = 0; + while (iteration < max_iteration && (faces.size() > 0 || nf > 0)) { + + if (verbose) std::cout << "[Fix Self-intersect face]" << std::endl << " -- Iteration " << iteration + 1 << std::endl; + vcg::tri::UpdateSelection::FaceClear(mesh); + if (faces.size() > 0) { + // Select self-intersect faces + for (fc = faces.begin(); fc != faces.end(); fc++) { + (*fc)->SetS(); + } + // Dilate the faces and vertices + for (uint16_t dilate_step = 0; dilate_step < iteration + 1; dilate_step++) { + vcg::tri::UpdateSelection::VertexFromFaceLoose(mesh); + vcg::tri::UpdateSelection::FaceFromVertexLoose(mesh); + } + // Select vertices from current faces and remove all selected + vcg::tri::UpdateSelection::VertexClear(mesh); + vcg::tri::UpdateSelection::VertexFromFaceStrict(mesh); + for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); ++it) { + if (!it->IsD() && it->IsS()) + vcg::tri::Allocator::DeleteFace(mesh, *it); + } + for (TMesh::VertexIterator it = mesh.vert.begin(); it != mesh.vert.end(); ++it) { + if (!it->IsD() && it->IsS()) + vcg::tri::Allocator::DeleteVertex(mesh, *it); + } + if (verbose) std::cout << " -- self-intersect faces: " << faces.size() << std::endl; + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + vcg::tri::Allocator::CompactEveryVector(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove faces [OK]" << std::endl; + vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + if (verbose) std::cout << " -- Remove small components " << minimum_diameter * mesh.bbox.Diag() << " [OK]" << std::endl; + vcg::tri::Clean::RemoveNonManifoldFace(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove non-manifold edges [OK]" << std::endl; + if (vcg::tri::Clean::CountNonManifoldEdgeFF(mesh) > 0) { + std::cout << "[Warning]: Fixed Self-intersecting failed: Mesh has some not 2-manifold edges" << std::endl; + return false; + } + vcg::tri::Hole::EarCuttingIntersectionFill>(mesh, maxSize, false); + if (verbose) std::cout << " -- Close holes [OK]" << std::endl; + vcg::tri::UpdateSelection::FaceClear(mesh); + vcg::tri::UpdateSelection::VertexClear(mesh); + } + + vcg::tri::UpdateSelection::FaceClear(mesh); + vcg::tri::UpdateSelection::VertexClear(mesh); + vcg::tri::UpdateFlags::FaceBorderFromNone(mesh); + vcg::tri::UpdateFlags::VertexBorderFromFaceBorder(mesh); + vcg::tri::UpdateSelection::FaceFromBorderFlag(mesh); + vcg::tri::UpdateSelection::VertexFromBorderFlag(mesh); + nf = vcg::tri::UpdateSelection::FaceCount(mesh); + nv = vcg::tri::UpdateSelection::VertexCount(mesh); + if (verbose) std::cout << " -- Count border edge v:" << nv << " f:" << nf << std::endl; + if (nf > 0) { + // Dilate the faces and vertices + //for (uint16_t dilate_step = 0; dilate_step < iteration + 1; dilate_step++) { + vcg::tri::UpdateSelection::VertexFromFaceLoose(mesh); + vcg::tri::UpdateSelection::FaceFromVertexLoose(mesh); + //} + vcg::tri::UpdateSelection::VertexClear(mesh); + vcg::tri::UpdateSelection::VertexFromFaceStrict(mesh); + for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); ++it) { + if (!it->IsD() && it->IsS()) + vcg::tri::Allocator::DeleteFace(mesh, *it); + } + for (TMesh::VertexIterator it = mesh.vert.begin(); it != mesh.vert.end(); ++it) { + if (!it->IsD() && it->IsS()) + vcg::tri::Allocator::DeleteVertex(mesh, *it); + } + vcg::tri::Allocator::CompactEveryVector(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove Border faces [OK]" << std::endl; + vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); + vcg::tri::Clean::RemoveUnreferencedVertex(mesh); + if (verbose) std::cout << " -- Remove small components " << minimum_diameter * mesh.bbox.Diag() << " [OK]" << std::endl; + vcg::tri::Clean::RemoveNonManifoldFace(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + if (verbose) std::cout << " -- Remove non-manifold edges [OK]" << std::endl; + if (vcg::tri::Clean::CountNonManifoldEdgeFF(mesh) > 0) { + std::cout << "[Warning]: Fixed Self-intersecting failed: Mesh has some not 2-manifold edges" << std::endl; + return false; + } + vcg::tri::Hole::EarCuttingIntersectionFill>(mesh, maxSize, false); + if (verbose) std::cout << " -- Close holes [OK]" << std::endl; + + vcg::tri::UpdateSelection::FaceClear(mesh); + vcg::tri::UpdateFlags::FaceBorderFromNone(mesh); + vcg::tri::UpdateFlags::VertexBorderFromFaceBorder(mesh); + vcg::tri::UpdateSelection::FaceFromBorderFlag(mesh); + vcg::tri::UpdateSelection::VertexFromBorderFlag(mesh); + nf = vcg::tri::UpdateSelection::FaceCount(mesh); } - i++; + + vcg::tri::Clean::SelfIntersections(mesh, faces); + iteration++; } + + vcg::tri::Allocator::CompactEveryVector(mesh); + vcg::tri::UpdateTopology::FaceFace(mesh); + + if (faces.size() > 0 && nf > 0) return false; + return true; } -#endif \ No newline at end of file + +inline void report_mesh(TMesh& mesh) { + int connectedComponentsNum = vcg::tri::Clean::CountConnectedComponents(mesh); + std::cout + << "[Topology Measurement] " << std::endl + << "-- Mesh is composed by " << connectedComponentsNum << " connected component(s)" << std::endl; + + int edgeNum = 0, edgeBorderNum = 0, edgeNonManifoldNum = 0; + vcg::tri::Clean::CountEdgeNum(mesh, edgeNum, edgeBorderNum, edgeNonManifoldNum); + int vertManifNum = vcg::tri::Clean::CountNonManifoldVertexFF(mesh, false); + + std::cout + << "-- border edge: " << edgeBorderNum << std::endl + << "-- non-manifold edge: " << edgeNonManifoldNum << std::endl + << "-- non-manifold vertex: " << vertManifNum << std::endl; + + if (edgeNonManifoldNum == 0 && vertManifNum == 0) { + int holeNum = vcg::tri::Clean::CountHoles(mesh); + int genus = vcg::tri::Clean::MeshGenus(mesh.vn, edgeNum, mesh.fn, holeNum, connectedComponentsNum); + + std::cout + << "-- Mesh is two-manifold " << std::endl + << "-- Mesh has " << holeNum << " holes" << std::endl + << "-- Genus is " << genus << std::endl; + } +} + +inline bool is_mesh_manifold(TMesh& mesh) { + int edgeNum = 0, edgeBorderNum = 0, edgeNonManifoldNum = 0; + vcg::tri::Clean::CountEdgeNum(mesh, edgeNum, edgeBorderNum, edgeNonManifoldNum); + int vertManifNum = vcg::tri::Clean::CountNonManifoldVertexFF(mesh, false); + return (edgeNonManifoldNum == 0 && vertManifNum == 0); +} \ No newline at end of file diff --git a/include/OptimalSlice.hpp b/include/OptimalSlice.hpp index fa76c3c..78b34f9 100644 --- a/include/OptimalSlice.hpp +++ b/include/OptimalSlice.hpp @@ -386,7 +386,7 @@ namespace slice { L.resize(grid_size + 2); for (size_t i = 0; i <= grid_size + 1; i++) L[i].clear(); // foreach triangle in mesh - // TODO: implement parallel version with tbb +#ifndef USE_PARALLEL for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); it++) { if (!it->IsD()) { @@ -397,6 +397,34 @@ namespace slice { L[i].push_back(triangle); } } +#else + tbb::spin_mutex writeMutex; + static tbb::affinity_partitioner ap; + tbb::parallel_for( + tbb::blocked_range(0, mesh.face.size()), + [&](const tbb::blocked_range r) { + // Prepare local_L + Layer _L(grid_size + 2); + _L.resize(grid_size + 2); + for (size_t i = 0; i <= grid_size + 1; i++) _L[i].clear(); + for (size_t i = r.begin(); i < r.end(); i++) { + if (!mesh.face[i].IsD()) { + Triangle triangle(mesh.face[i]); + size_t level = size_t(ceil((triangle.min[direction] - P[1]) / delta) + 1); + assert(level > 0 && level <= grid_size + 1); + _L[level].push_back(triangle); + } + } + { + tbb::spin_mutex::scoped_lock lock(writeMutex); + for (size_t i = 0; i <= grid_size + 1; i++) { + L[i].reserve(L[i].size() + _L[i].size()); + L[i].insert(L[i].end(), _L[i].begin(), _L[i].end()); + } + } + }, ap + ); +#endif } inline Point3d compute_point_at_plane(Point3d v0, Point3d v1, double position, int direction = Direction::Z) { diff --git a/include/ProgressBar.hpp b/include/ProgressBar.hpp index 608ff2a..ccb6a2b 100644 --- a/include/ProgressBar.hpp +++ b/include/ProgressBar.hpp @@ -23,6 +23,8 @@ class ProgressBar { unsigned int operator++() { return ++ticks; } ProgressBar& operator+=(const unsigned int tick) { ticks += tick; return *this; } + void update(const unsigned int tick) { ticks = tick; } + void display() const { float progress = (float)ticks / total_ticks; diff --git a/include/Scaffolder_2.h b/include/Scaffolder_2.h index 48e96a6..973542e 100644 --- a/include/Scaffolder_2.h +++ b/include/Scaffolder_2.h @@ -118,145 +118,25 @@ inline void marching_cube(TMesh &mesh, Eigen::MatrixXd &Fxyz, Eigen::RowVector3i } } -inline void clean_mesh(TMesh& mesh, double minimum_diameter, uint16_t smooth_step, bool verbose = true) { - if (verbose) std::cout << "[libVCG Cleaning] "; - vcg::tri::Clean::RemoveDuplicateVertex(mesh); - vcg::tri::Allocator::CompactEveryVector(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - vcg::tri::Clean::RemoveDuplicateFace(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - vcg::tri::Clean::RemoveZeroAreaFace(mesh); - vcg::tri::Clean::RemoveUnreferencedVertex(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - vcg::tri::UpdateBounding::Box(mesh); - vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); - vcg::tri::Clean::RemoveUnreferencedVertex(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - vcg::tri::UpdateBounding::Box(mesh); - if (verbose) std::cout << "OK" << std::endl; - if (smooth_step > 0) { - if (verbose) std::cout << "[Laplacian smoothing] "; - vcg::tri::Smooth::VertexCoordLaplacian(mesh, smooth_step, false, true); - if (verbose) std::cout << "OK" << std::endl; +inline void mesh_to_eigen_vector(TMesh& mesh, Eigen::MatrixXd& V, Eigen::MatrixXi& F) { + V.resize(mesh.VN(), 3); + size_t i = 0; + std::vector vertexId(mesh.vert.size()); + for (TMesh::VertexIterator it = mesh.vert.begin(); it != mesh.vert.end(); ++it) if (!it->IsD()) { + vertexId[it - mesh.vert.begin()] = i; + vcg::Point3d point = it->P(); + V(i, 0) = point[0]; + V(i, 1) = point[1]; + V(i, 2) = point[2]; + i++; } - vcg::tri::Clean::MergeCloseVertex(mesh, SLICE_PRECISION*1000); - vcg::tri::Clean::RemoveUnreferencedVertex(mesh); - vcg::tri::Allocator::CompactEveryVector(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); -} - -inline void fix_self_intersect_mesh(TMesh& mesh, double minimum_diameter, uint16_t max_iteration = 10, bool verbose = false) { - - std::vector faces; - vcg::tri::Clean::SelfIntersections(mesh, faces); - - uint16_t iteration = 0; - int maxSize = mesh.bbox.SquaredDiag(); - size_t nf = 0; - while (iteration < max_iteration && (faces.size() > 0 || nf > 0)) { - - if (verbose) std::cout << "[Fix Self-intersect face]" << std::endl << " -- Iteration " << iteration + 1 << std::endl; - - if (faces.size() > 0) { - for (size_t i = 0; i < faces.size(); i++) { - if (!faces[i]->IsD()) - vcg::tri::Allocator::DeleteFace(mesh, *(faces[i])); - } - if (verbose) std::cout << " -- self-intersect faces: " << faces.size() << std::endl; - vcg::tri::Clean::RemoveUnreferencedVertex(mesh); - vcg::tri::Allocator::CompactEveryVector(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - vcg::tri::UpdateBounding::Box(mesh); - if (verbose) std::cout << " -- Remove faces [OK]" << std::endl; - vcg::tri::Clean::RemoveSmallConnectedComponentsDiameter(mesh, minimum_diameter * mesh.bbox.Diag()); - vcg::tri::Clean::RemoveUnreferencedVertex(mesh); - if (verbose) std::cout << " -- Remove small components " << minimum_diameter * mesh.bbox.Diag() << " [OK]" << std::endl; - vcg::tri::Clean::RemoveNonManifoldFace(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - if (verbose) std::cout << " -- Remove non-manifold edges [OK]" << std::endl; - if (vcg::tri::Clean::CountNonManifoldEdgeFF(mesh) > 0) { - std::cout << "[Warning]: Fixed Self-intersecting failed: Mesh has some not 2-manifold edges" << std::endl; - return; - } - vcg::tri::Hole::EarCuttingIntersectionFill>(mesh, maxSize, false); - if (verbose) std::cout << " -- Close holes [OK]" << std::endl; - } - - vcg::tri::UpdateFlags::FaceBorderFromNone(mesh); - vcg::tri::UpdateFlags::VertexBorderFromFaceBorder(mesh); - vcg::tri::UpdateSelection::FaceFromBorderFlag(mesh); - vcg::tri::UpdateSelection::VertexFromBorderFlag(mesh); - nf = vcg::tri::UpdateSelection::FaceCount(mesh); - if (nf > 0) { - vcg::tri::UpdateSelection::VertexFromFaceLoose(mesh); - vcg::tri::UpdateSelection::FaceFromVertexLoose(mesh); - vcg::tri::UpdateSelection::VertexClear(mesh); - vcg::tri::UpdateSelection::VertexFromFaceStrict(mesh); - for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); ++it) { - if (!it->IsD() && it->IsS()) - vcg::tri::Allocator::DeleteFace(mesh, *it); - } - for (TMesh::VertexIterator it = mesh.vert.begin(); it != mesh.vert.end(); ++it) { - if (!it->IsD() && it->IsS()) - vcg::tri::Allocator::DeleteVertex(mesh, *it); - } - vcg::tri::Allocator::CompactEveryVector(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - if (verbose) std::cout << " -- Remove Border faces [OK]" << std::endl; - vcg::tri::Clean::RemoveNonManifoldFace(mesh); - vcg::tri::UpdateBounding::Box(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); - if (verbose) std::cout << " -- Remove non-manifold edges [OK]" << std::endl; - vcg::tri::Hole::EarCuttingIntersectionFill>(mesh, maxSize, false); - if (verbose) std::cout << " -- Close holes [OK]" << std::endl; - - vcg::tri::UpdateFlags::FaceBorderFromNone(mesh); - vcg::tri::UpdateFlags::VertexBorderFromFaceBorder(mesh); - vcg::tri::UpdateSelection::FaceFromBorderFlag(mesh); - vcg::tri::UpdateSelection::VertexFromBorderFlag(mesh); - nf = vcg::tri::UpdateSelection::FaceCount(mesh); + // Faces to Eigen matrixXi F1 + i = 0; + F.resize(mesh.FN(), mesh.face.begin()->VN()); + for (TMesh::FaceIterator it = mesh.face.begin(); it != mesh.face.end(); ++it) if (!it->IsD()) { + for (int k = 0; k < it->VN(); k++) { + F(i, k) = vertexId[vcg::tri::Index(mesh, it->V(k))]; } - - vcg::tri::Clean::SelfIntersections(mesh, faces); - iteration++; - } - vcg::tri::Allocator::CompactEveryVector(mesh); - - while (iteration < max_iteration && nf > 0) { - if (verbose) std::cout << "[Fix Border Edges and holes]" << std::endl << " -- Iteration " << iteration + 1 << std::endl; - - - - iteration++; - } - vcg::tri::UpdateBounding::Box(mesh); - vcg::tri::UpdateTopology::FaceFace(mesh); -} - -inline void report_mesh(TMesh& mesh) { - int connectedComponentsNum = vcg::tri::Clean::CountConnectedComponents(mesh); - std::cout - << "[Topology Measurement] " << std::endl - << "-- Mesh is composed by " << connectedComponentsNum << " connected component(s)" << std::endl; - - int edgeNum = 0, edgeBorderNum = 0, edgeNonManifoldNum = 0; - vcg::tri::Clean::CountEdgeNum(mesh, edgeNum, edgeBorderNum, edgeNonManifoldNum); - int vertManifNum = vcg::tri::Clean::CountNonManifoldVertexFF(mesh, true); - - if (edgeNonManifoldNum == 0 && vertManifNum == 0) { - int holeNum = vcg::tri::Clean::CountHoles(mesh); - int genus = vcg::tri::Clean::MeshGenus(mesh.vn, edgeNum, mesh.fn, holeNum, connectedComponentsNum); - - std::cout - << "-- Mesh is two-manifold " << std::endl - << "-- Mesh has " << holeNum << " holes" << std::endl - << "-- Genus is " << genus << std::endl; + i++; } -} - -inline bool is_mesh_manifold(TMesh &mesh) { - int edgeNum = 0, edgeBorderNum = 0, edgeNonManifoldNum = 0; - vcg::tri::Clean::CountEdgeNum(mesh, edgeNum, edgeBorderNum, edgeNonManifoldNum); - int vertManifNum = vcg::tri::Clean::CountNonManifoldVertexFF(mesh, true); - return edgeNonManifoldNum == 0 && vertManifNum == 0; } \ No newline at end of file diff --git a/include/implicit_function.h b/include/implicit_function.h index 17649aa..e881a12 100644 --- a/include/implicit_function.h +++ b/include/implicit_function.h @@ -233,12 +233,6 @@ class Implicit_function : public Function { } }; -inline void to_lower(std::string& s) { - std::locale loc; - for (std::string::size_type i = 0; i < s.length(); ++i) - s[i] = std::tolower(s[i], loc); -} - Function* isosurface(std::string name, FT t) { if (name == "empty") { return new Fixed(1); diff --git a/include/utils.h b/include/utils.h index 8ca84e0..ec54486 100644 --- a/include/utils.h +++ b/include/utils.h @@ -1,5 +1,7 @@ #pragma once +#include + #ifdef _WIN32 #include #include @@ -69,7 +71,7 @@ namespace util { // Find the last dot, if any. size_t dotIdx = path.find_last_of("."); - if (dotIdx != string::npos) + if (dotIdx != std::string::npos) { // Find the last directory separator, if any. size_t dirSepIdx = path.find_last_of("/\\"); @@ -86,4 +88,10 @@ namespace util { return ext; } + + inline void to_lower(std::string& s) { + std::locale loc; + for (std::string::size_type i = 0; i < s.length(); ++i) + s[i] = std::tolower(s[i], loc); + } } \ No newline at end of file diff --git a/src/FixSelfIntersect.cpp b/src/FixSelfIntersect.cpp new file mode 100644 index 0000000..1127432 --- /dev/null +++ b/src/FixSelfIntersect.cpp @@ -0,0 +1,142 @@ +#include +#include +#include + +#include "cxxopts.hpp" +#include "ProgressBar.hpp" +#include "utils.h" +#include "Mesh.h" + +ProgressBar import_progress(100, 40); + +bool import_callback(int pos, const char* str) { + if (pos % 10 == 0) { + import_progress.update(pos); + import_progress.display(); + } + if (pos >= 100) import_progress.done(); + return true; +} + +int main(int argc, char* argv[]) +{ + bool verbose = true; + double minimum_diameter = 0.25; + uint16_t max_iteration = 5; + + std::string input_file = ""; + std::string format = ""; + std::string output_file = ""; + std::string output_format = ""; + + try { + cxxopts::Options options("Fix Self Intersect", "Automatic fix self-intersect"); + options.positional_help("INPUT OUTPUT MAX_ITERATION").show_positional_help(); + options.add_options() + ("h,help", "Print help") + ("i,input", "Input file (STL,PLY,OFF,OBJ)", cxxopts::value(), "FILE") + ("o,output", "Output file (STL,PLY,OFF,OBJ)", cxxopts::value(), "FILE") + ("n,max_iteration", "Max iteration [default: 5]", cxxopts::value(), "UINT16"); + options.parse_positional({ "input", "output", "max_iteration" }); + bool isEmptyOption = (argc == 1); + cxxopts::ParseResult result = options.parse(argc, argv); + if (isEmptyOption || result.count("help")) { + std::cout << options.help() << std::endl; + return 0; + } + // Requirment + if (result.count("input")) { + input_file = result["input"].as(); + std::string ext = util::PathGetExtension(input_file); + if (!ext.empty()) { + format = ext.substr(1); + util::to_lower(format); + } + } + else { + std::cout << "Missing Input file" << std::endl; + return 1; + } + + // Optional parameter: output_file default to input_file + if (result.count("output")) { + output_file = result["output"].as(); + std::string ext = util::PathGetExtension(output_file); + if (!ext.empty()) { + output_format = ext.substr(1); + util::to_lower(output_format); + } + } + else { + output_file = input_file; + output_format = format; + } + + if (format != "ply" && format != "obj" && format != "stl" && format != "off") { + std::cout << "Invalid input extension: " << format << std::endl; + return 1; + } + + if (output_format != "ply" && output_format != "obj" && output_format != "stl" && output_format != "off") { + std::cout << "Invalid output extension: " << output_format << std::endl; + return 1; + } + + if (result.count("max_iteration")) max_iteration = result["max_iteration"].as(); + } + catch (const cxxopts::OptionException & ex) { + std::cout << "Error parsing options: " << ex.what() << std::endl; + return 1; + } + + { + TMesh mesh; + { + if (verbose) std::cout << "Import " << format << std::endl; + int loadmark = 0; + if (format == "stl") { + vcg::tri::io::ImporterSTL::Open(mesh, input_file.c_str(), loadmark, import_callback); + } + else if (format == "ply") { + vcg::tri::io::ImporterPLY::Open(mesh, input_file.c_str(), import_callback); + } + else if (format == "off") { + vcg::tri::io::ImporterOFF::Open(mesh, input_file.c_str(), import_callback); + } + else if (format == "obj") { + vcg::tri::io::ImporterOBJ::Open(mesh, input_file.c_str(), loadmark, import_callback); + } + if (verbose) std::cout << std::endl; + } + vcg::tri::UpdateBounding::Box(mesh); + + bool is_success = fix_self_intersect_mesh(mesh, minimum_diameter, max_iteration, verbose); + + bool is_manifold = is_mesh_manifold(mesh); + if (!is_manifold) { + is_success = fix_non_manifold(mesh, minimum_diameter, max_iteration, verbose); + } + if(verbose) report_mesh(mesh); + + if (is_success) { + if (verbose) std::cout << "Export " << output_file; + if (output_format == "stl") { + vcg::tri::io::ExporterSTL::Save(mesh, output_file.c_str(), true); + } + else if (output_format == "ply") { + vcg::tri::io::ExporterPLY::Save(mesh, output_file.c_str(), false); + } + else if (output_format == "off") { + vcg::tri::io::ExporterOFF::Save(mesh, output_file.c_str()); + } + else if (output_format == "obj") { + vcg::tri::io::ExporterOBJ::Save(mesh, output_file.c_str(), 0); + } + if (verbose) std::cout << " [OK]" << std::endl; + } + else { + if (verbose) std::cout << "Sorry, fail to fix" << std::endl; + } + } + return 0; +} \ No newline at end of file diff --git a/src/Main.cpp b/src/Main.cpp index 83a1f91..80d5723 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -9,7 +9,7 @@ int main(int argc, char* argv[]) bool is_export_microstructure = false; bool is_export_feret = false; bool is_export_inverse = false; - bool is_build_inverse = true; + bool is_build_inverse = false; bool is_fix_self_intersect = false; bool no_output = false; uint16_t grid_offset = 3; @@ -51,7 +51,7 @@ int main(int argc, char* argv[]) ("method", "Method of microstructure analysis: 0 (Image processing technique) or 1 (Slice contour technique) [default: 0]", cxxopts::value(), "0,1") ("slice_grid", "Slice Grid used in microstructure analysis [default: 100]", cxxopts::value(), "INT") ("output_inverse", "additional output inverse scaffold [default: false]") - ("inverse", "Enable build inverse 3D scaffold (for pore connectivity analysis) [default: true]") + ("inverse", "Enable build inverse 3D scaffold (for pore connectivity analysis) [default: false]") ("dirty", "Disable autoclean [default false]") ("fix_self_intersect", "Experimental fix self-intersect faces [default: false]") ("minimum_diameter", "used for removing small orphaned (between 0-1) [default: 0.25]", cxxopts::value(), "DOUBLE"); @@ -97,7 +97,7 @@ int main(int argc, char* argv[]) } if (result.count("output")) { filename = result["output"].as(); - to_lower(filename); + util::to_lower(filename); std::string ext = util::PathGetExtension(filename); if (!ext.empty()) { filename = filename.substr(0, filename.size()-ext.size()); @@ -118,8 +118,8 @@ int main(int argc, char* argv[]) if (result.count("fix_self_intersect")) is_fix_self_intersect = result["fix_self_intersect"].as(); if (result.count("slice_grid")) slice_grid = result["slice_grid"].as(); - to_lower(surface); - to_lower(format); + util::to_lower(surface); + util::to_lower(format); if (format != "ply" && format != "obj" && format != "stl" && format != "off") { std::cout << "Invalid format: " << format << std::endl; @@ -166,7 +166,6 @@ int main(int argc, char* argv[]) result << "Surface,coff,shell,thickness,grid_size,grid_offset,smooth_step,input_file,avg_min_feret,avg_max_feret," << "min_min_feret,q1_min_feret,q2_min_feret,q3_min_feret,max_min_feret," << "min_max_feret,q1_max_feret,q2_max_feret,q3_max_feret,max_max_feret," - << "vol,surface_area,porosity,surface_area_ratio,#vertices,#faces," << "min_square,q1_square,q2_square,q3_square,max_square," << "min_circle,q1_circle,q2_circle,q3_circle,max_circle," << "min_triangle,q1_triangle,q2_triangle,q3_triangle,max_triangle," @@ -336,6 +335,9 @@ int main(int argc, char* argv[]) if (is_build_inverse) clean_mesh(inverse_mesh, minimum_diameter, 0, false); } bool is_manifold = is_mesh_manifold(mesh); + if (!is_manifold) { + is_manifold = fix_non_manifold(mesh, minimum_diameter, 3, verbose); + } if (!is_manifold && !verbose) { std::cout << "[Warning] Mesh is not two manifold" << std::endl; } @@ -560,7 +562,7 @@ int main(int argc, char* argv[]) if (!verbose) { result << ",,,," << mesh.VN() << ',' << mesh.FN() << std::endl; } - std::cout << "[Warning] The scaffolder isn't a manifold. The grid_offset should have been increased" << std::endl; + std::cout << "[Warning] The scaffold isn't a manifold. The grid_offset should be increased or enable --fix_self_intersect option" << std::endl; } if (!no_output) {