From 2090fa9e18458ad12d9cf5dce961c7b1c3abcf76 Mon Sep 17 00:00:00 2001 From: cDc Date: Sun, 1 Sep 2024 16:55:50 +0300 Subject: [PATCH] mesh: implement RemoveDegenerateFaces() and RemoveDuplicatedVertices() --- apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp | 11 +- apps/ReconstructMesh/ReconstructMesh.cpp | 2 +- apps/Viewer/Scene.cpp | 2 +- libs/Common/List.h | 74 +++---- libs/Common/Types.inl | 62 +++++- libs/MVS/Mesh.cpp | 245 ++++++++++++++++++++--- libs/MVS/Mesh.h | 16 +- libs/MVS/SceneRefine.cpp | 4 +- libs/MVS/SceneRefineCUDA.cpp | 4 +- libs/MVS/SceneTexture.cpp | 6 +- 10 files changed, 337 insertions(+), 89 deletions(-) diff --git a/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp b/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp index d9ff4489c..2a8ded5a2 100644 --- a/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp +++ b/apps/InterfaceCOLMAP/InterfaceCOLMAP.cpp @@ -256,13 +256,12 @@ struct Camera { struct CameraHash { size_t operator()(const Camera& camera) const { - const size_t h1(std::hash()(camera.model)); - const size_t h2(std::hash()(camera.width)); - const size_t h3(std::hash()(camera.height)); - size_t h(h1 ^ ((h2 ^ (h3 << 1)) << 1)); + size_t seed = std::hash()(camera.model); + std::hash_combine(seed, camera.width); + std::hash_combine(seed, camera.height); for (REAL p: camera.params) - h = std::hash()(p) ^ (h << 1); - return h; + std::hash_combine(seed, p); + return seed; } }; struct CameraEqualTo { diff --git a/apps/ReconstructMesh/ReconstructMesh.cpp b/apps/ReconstructMesh/ReconstructMesh.cpp index e4fc7bf6a..7d3e9853a 100644 --- a/apps/ReconstructMesh/ReconstructMesh.cpp +++ b/apps/ReconstructMesh/ReconstructMesh.cpp @@ -315,7 +315,7 @@ bool Export3DProjections(Scene& scene, const String& inputFileName) { const Mesh::Octree octree(scene.mesh.vertices, [](Mesh::Octree::IDX_TYPE size, Mesh::Octree::Type /*radius*/) { return size > 256; }); - scene.mesh.ListIncidenteFaces(); + scene.mesh.ListIncidentFaces(); // save 3D coord in the output file const Image& imgToExport = scene.images[imgID]; diff --git a/apps/Viewer/Scene.cpp b/apps/Viewer/Scene.cpp index 161c33e17..8053bb5cc 100644 --- a/apps/Viewer/Scene.cpp +++ b/apps/Viewer/Scene.cpp @@ -84,7 +84,7 @@ class EVTComputeOctree : public Event Scene::OctreeMesh octMesh(scene.mesh.vertices, [](Scene::OctreeMesh::IDX_TYPE size, Scene::OctreeMesh::Type /*radius*/) { return size > 256; }); - scene.mesh.ListIncidenteFaces(); + scene.mesh.ListIncidentFaces(); pScene->octMesh.Swap(octMesh); } else if (!scene.pointcloud.IsEmpty()) { diff --git a/libs/Common/List.h b/libs/Common/List.h index 7bbd5c078..bf45e47a2 100644 --- a/libs/Common/List.h +++ b/libs/Common/List.h @@ -122,14 +122,14 @@ class cList } // construct a list containing size initialized elements - cList(IDX size) : _size(size), _vectorSize(size), _vector((TYPE*)operator new[] (size * sizeof(TYPE))) + cList(IDX size) : _size(size), _vectorSize(size), _vector((TYPE*)operator new[] (static_cast(size) * sizeof(TYPE))) { ASSERT(size > 0 && size < NO_INDEX); _ArrayConstruct(_vector, size); } // construct a list containing size initialized elements and allocated space for _reserved elements - cList(IDX size, IDX _reserved) : _size(size), _vectorSize(_reserved), _vector((TYPE*)operator new[] (_reserved * sizeof(TYPE))) + cList(IDX size, IDX _reserved) : _size(size), _vectorSize(_reserved), _vector((TYPE*)operator new[] (static_cast(_reserved) * sizeof(TYPE))) { ASSERT(_reserved >= size && _reserved < NO_INDEX); _ArrayConstruct(_vector, size); @@ -142,23 +142,21 @@ class cList ASSERT(_size == 0); return; } - _vector = (TYPE*)(operator new[] (_vectorSize * sizeof(TYPE))); + _vector = (TYPE*)(operator new[] (static_cast(_vectorSize) * sizeof(TYPE))); _ArrayCopyConstruct(_vector, rList._vector, _size); } - #ifdef _SUPPORT_CPP11 - // copy constructor: creates a move-copy of the given list + // move constructor: creates a move-copy of the given list cList(cList&& rList) : _size(rList._size), _vectorSize(rList._vectorSize), _vector(rList._vector) { rList._Init(); } - #endif // constructor a list from a raw data array explicit inline cList(TYPE* pDataBegin, TYPE* pDataEnd) : _size((IDX)(pDataEnd-pDataBegin)), _vectorSize(_size) { if (_vectorSize == 0) return; - _vector = (TYPE*) operator new[] (_vectorSize * sizeof(TYPE)); + _vector = (TYPE*) operator new[] (static_cast(_vectorSize) * sizeof(TYPE)); _ArrayCopyConstruct(_vector, pDataBegin, _size); } @@ -172,6 +170,11 @@ class cList _Release(); } + // move the content from the given list + inline cList& operator=(cList&& rList) + { + return CopyOfRemove(rList); + } // copy the content from the given list inline cList& operator=(const cList& rList) { @@ -185,16 +188,14 @@ class cList if (bForceResize || _vectorSize < rList._vectorSize) { _Release(); _vectorSize = rList._vectorSize; - _vector = (TYPE*) operator new[] (_vectorSize * sizeof(TYPE)); + _vector = (TYPE*) operator new[] (static_cast(_vectorSize) * sizeof(TYPE)); _ArrayCopyConstruct(_vector, rList._vector, rList._size); + } else if (_size >= rList._size) { + _ArrayDestruct(_vector+rList._size, _size-rList._size); + _ArrayCopyRestrict(_vector, rList._vector, rList._size); } else { - if (_size >= rList._size) { - _ArrayDestruct(_vector+rList._size, _size-rList._size); - _ArrayCopyRestrict(_vector, rList._vector, rList._size); - } else { - _ArrayCopyRestrict(_vector, rList._vector, _size); - _ArrayCopyConstruct(_vector+_size, rList._vector+_size, rList._size-_size); - } + _ArrayCopyRestrict(_vector, rList._vector, _size); + _ArrayCopyConstruct(_vector+_size, rList._vector+_size, rList._size-_size); } _size = rList._size; return *this; @@ -207,16 +208,14 @@ class cList if (bForceResize || _vectorSize < nSize) { _Release(); _vectorSize = nSize; - _vector = (TYPE*) operator new[] (_vectorSize * sizeof(TYPE)); + _vector = (TYPE*) operator new[] (static_cast(_vectorSize) * sizeof(TYPE)); _ArrayCopyConstruct(_vector, pData, nSize); + } else if (_size >= nSize) { + _ArrayDestruct(_vector+nSize, _size-nSize); + _ArrayCopyRestrict(_vector, pData, nSize); } else { - if (_size >= nSize) { - _ArrayDestruct(_vector+nSize, _size-nSize); - _ArrayCopyRestrict(_vector, pData, nSize); - } else { - _ArrayCopyRestrict(_vector, pData, _size); - _ArrayCopyConstruct(_vector+_size, pData+_size, nSize-_size); - } + _ArrayCopyRestrict(_vector, pData, _size); + _ArrayCopyConstruct(_vector+_size, pData+_size, nSize-_size); } _size = nSize; return *this; @@ -231,8 +230,7 @@ class cList _size = rList._size; _vectorSize = rList._vectorSize; _vector = rList._vector; - rList._vector = NULL; - rList._size = rList._vectorSize = 0; + rList._Init(); return *this; } @@ -318,7 +316,7 @@ class cList // Set the allocated memory (normally used for types without constructor). inline void Memset(uint8_t val) { - memset(_vector, val, _size * sizeof(TYPE)); + memset(_vector, val, static_cast(_size) * sizeof(TYPE)); } inline void MemsetValue(ARG_TYPE val) { @@ -336,7 +334,7 @@ class cList _vector = NULL; return; } - _vector = (TYPE*) operator new[] (newSize * sizeof(TYPE)); + _vector = (TYPE*) operator new[] (static_cast(newSize) * sizeof(TYPE)); _ArrayConstruct(_vector, newSize); } @@ -408,7 +406,11 @@ class cList } inline size_t GetDataSize() const { - return sizeof(TYPE)*_size; + return sizeof(TYPE)*static_cast(_size); + } + inline size_t GetMemorySize() const + { + return sizeof(cList)+sizeof(TYPE)*static_cast(_vectorSize); } inline TYPE* Begin() const @@ -1271,15 +1273,15 @@ class cList // grow by 50% or at least to minNewVectorSize IDX expoVectorSize(_vectorSize + (_vectorSize>>1)); // cap growth for very large vectors - const IDX maxGrowCapacity(3*1024*1024*1024ull/*3GB*/); - const IDX growCapacity((expoVectorSize - _vectorSize) * sizeof(TYPE)); + const size_t maxGrowCapacity(3*1024*1024*1024ull/*3GB*/); + const size_t growCapacity(static_cast(expoVectorSize - _vectorSize) * sizeof(TYPE)); if (growCapacity > maxGrowCapacity) - expoVectorSize = _vectorSize + maxGrowCapacity / sizeof(TYPE); + expoVectorSize = _vectorSize + static_cast(maxGrowCapacity / sizeof(TYPE)); // allocate a larger chunk of memory, copy the data and delete the old chunk if (newVectorSize < expoVectorSize) newVectorSize = expoVectorSize; TYPE* const tmp(_vector); - _vector = (TYPE*) operator new[] (newVectorSize * sizeof(TYPE)); + _vector = (TYPE*) operator new[] (static_cast(newVectorSize) * sizeof(TYPE)); _ArrayMoveConstruct(_vector, tmp, _size); _vectorSize = newVectorSize; operator delete[] (tmp); @@ -1301,7 +1303,7 @@ class cList _vector = NULL; } else { TYPE* const tmp(_vector); - _vector = (TYPE*) operator new[] (_vectorSize * sizeof(TYPE)); + _vector = (TYPE*) operator new[] (static_cast(_vectorSize) * sizeof(TYPE)); _ArrayMoveConstruct(_vector, tmp, _vectorSize); operator delete[] (tmp); } @@ -1363,7 +1365,7 @@ class cList (src+n)->~TYPE(); } } else { - const size_t _size(sizeof(TYPE)*n); + const size_t _size(sizeof(TYPE)*static_cast(n)); if (bRestrict) memcpy((void*)dst, (const void*)src, _size); else @@ -1380,7 +1382,7 @@ class cList (src+n)->~TYPE(); } } else { - const size_t _size(sizeof(TYPE)*n); + const size_t _size(sizeof(TYPE)*static_cast(n)); if (useConstruct == 1) while (n--) (dst+n)->~TYPE(); @@ -1410,7 +1412,7 @@ class cList typedef std::vector VectorType; inline cList(const VectorType& rList) { CopyOf(&rList[0], rList.size()); } #ifdef _SUPPORT_CPP11 - inline cList(std::initializer_list l) : _size(0), _vectorSize((size_type)l.size()), _vector(NULL) { ASSERT(l.size() l) : _size(0), _vectorSize((size_type)l.size()), _vector(NULL) { ASSERT(l.size()(_vectorSize)*sizeof(Type)); const Type* first(l.begin()); do new(_vector + _size++) Type(*first++); while (first!=l.end()); } #endif inline bool empty() const { return IsEmpty(); } inline size_type size() const { return GetSize(); } diff --git a/libs/Common/Types.inl b/libs/Common/Types.inl index 360a7c0f1..c0ca1e8fe 100644 --- a/libs/Common/Types.inl +++ b/libs/Common/Types.inl @@ -13,17 +13,63 @@ namespace std { -// Specializations for unordered containers -template <> struct hash -{ - typedef SEACAVE::ImageRef argument_type; - typedef size_t result_type; - result_type operator()(const argument_type& v) const { - return std::hash()((const uint64_t&)v); +// combine hash values (as in boost) +namespace { +template +inline void hash_combine(std::size_t& seed, T const& v) { + seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} +template ::value - 1> +struct HashValueImpl { + static void apply(size_t& seed, Tuple const& tuple) { + HashValueImpl::apply(seed, tuple); + hash_combine(seed, std::get(tuple)); + } +}; +template +struct HashValueImpl { + static void apply(size_t& seed, Tuple const& tuple) { hash_combine(seed, std::get<0>(tuple)); } +}; +} // namespace + +// hash specialization for pairs/tuples +template +struct hash> { + std::size_t operator()(const std::pair& x) const { + size_t seed = std::hash()(x.first); + hash_combine(seed, x.second); + return seed; + } +}; +template +struct hash> { + size_t operator()(const std::tuple& t) const { + size_t seed = 0; + HashValueImpl>::apply(seed, t); + return seed; + } +}; + +// hash specializations for OpenCV points +template +struct hash> { + size_t operator()(const cv::Point_& v) const { + size_t seed = std::hash()(v.x); + std::hash_combine(seed, v.y); + return seed; + } +}; +template +struct hash> { + size_t operator()(const cv::Point3_& v) const { + size_t seed = std::hash()(v.x); + std::hash_combine(seed, v.y); + std::hash_combine(seed, v.z); + return seed; } }; -// Adds the given key-value pair in the map, overwriting the current value if the key exists +// adds the given key-value pair in the map, overwriting the current value if the key exists template void MapPut(std::map* map, const Key& key, const T& value) { auto result = map->emplace(key, value); diff --git a/libs/MVS/Mesh.cpp b/libs/MVS/Mesh.cpp index 1b26bc53e..936f6db60 100644 --- a/libs/MVS/Mesh.cpp +++ b/libs/MVS/Mesh.cpp @@ -110,15 +110,19 @@ void Mesh::Release() } // Release void Mesh::ReleaseExtra() { + ReleaseComputable(); vertexNormals.Release(); - vertexVertices.Release(); - vertexFaces.Release(); - vertexBoundary.Release(); faceNormals.Release(); - faceFaces.Release(); faceTexcoords.Release(); texturesDiffuse.Release(); } // ReleaseExtra +void Mesh::ReleaseComputable() +{ + vertexVertices.Release(); + vertexFaces.Release(); + vertexBoundary.Release(); + faceFaces.Release(); +} // ReleaseComputable void Mesh::EmptyExtra() { vertexNormals.Empty(); @@ -130,7 +134,7 @@ void Mesh::EmptyExtra() faceTexcoords.Empty(); texturesDiffuse.Empty(); } // EmptyExtra -void Mesh::Swap(Mesh& rhs) +Mesh& Mesh::Swap(Mesh& rhs) { vertices.Swap(rhs.vertices); faces.Swap(rhs.faces); @@ -143,18 +147,21 @@ void Mesh::Swap(Mesh& rhs) faceTexcoords.Swap(rhs.faceTexcoords); faceTexindices.Swap(rhs.faceTexindices); std::swap(texturesDiffuse, rhs.texturesDiffuse); + return *this; } // Swap // combine this mesh with the given mesh, without removing duplicate vertices -void Mesh::Join(const Mesh& mesh) +Mesh& Mesh::Join(const Mesh& mesh) { ASSERT(!HasTexture() && !mesh.HasTexture()); + if (mesh.IsEmpty()) + return *this; vertexVertices.Release(); vertexFaces.Release(); vertexBoundary.Release(); faceFaces.Release(); if (IsEmpty()) { *this = mesh; - return; + return *this; } const VIndex offsetV(vertices.size()); vertices.Join(mesh.vertices); @@ -163,6 +170,7 @@ void Mesh::Join(const Mesh& mesh) for (const Face& face: mesh.faces) faces.emplace_back(face.x+offsetV, face.y+offsetV, face.z+offsetV); faceNormals.Join(mesh.faceNormals); + return *this; } /*----------------------------------------------------------------*/ @@ -171,7 +179,7 @@ bool Mesh::IsWatertight() { if (vertexBoundary.empty()) { if (vertexFaces.empty()) - ListIncidenteFaces(); + ListIncidentFaces(); ListBoundaryVertices(); } for (const bool b : vertexBoundary) @@ -219,7 +227,7 @@ Mesh::Vertex Mesh::GetCenter() const // extract array of vertices incident to each vertex -void Mesh::ListIncidenteVertices() +void Mesh::ListIncidentVertices() { vertexVertices.clear(); vertexVertices.resize(vertices.size()); @@ -237,15 +245,17 @@ void Mesh::ListIncidenteVertices() } // extract the (ordered) array of triangles incident to each vertex -void Mesh::ListIncidenteFaces() +void Mesh::ListIncidentFaces() { vertexFaces.clear(); vertexFaces.resize(vertices.size()); - FOREACH(i, faces) { - const Face& face = faces[i]; + FOREACH(iF, faces) { + const Face& face = faces[iF]; for (int v=0; v<3; ++v) { - ASSERT(vertexFaces[face[v]].Find(i) == FaceIdxArr::NO_INDEX); - vertexFaces[face[v]].emplace_back(i); + FaceIdxArr& vfs = vertexFaces[face[v]]; + ASSERT(vfs.Find(iF) == FaceIdxArr::NO_INDEX || vfs.Find(iF) == vfs.size()-1/*for degenerate faces*/); + if (vfs.empty() || vfs.back() != iF) + vfs.emplace_back(iF); } } } @@ -254,7 +264,7 @@ void Mesh::ListIncidenteFaces() // each triple describes the adjacent face triangles for a given face // in the following edge order: v1v2, v2v3, v3v1; // NO_ID indicates there is no adjacent face on that edge -void Mesh::ListIncidenteFaceFaces() +void Mesh::ListIncidentFaceFaces() { ASSERT(vertexFaces.size() == vertices.size()); struct inserter_data_t { @@ -291,7 +301,7 @@ void Mesh::ListIncidenteFaceFaces() } // check each vertex if it is at the boundary or not -// (make sure you called ListIncidenteFaces() before) +// (make sure you called ListIncidentFaces() before) void Mesh::ListBoundaryVertices() { vertexBoundary.clear(); @@ -408,9 +418,9 @@ void Mesh::SmoothNormalFaces(float fMaxGradient, float fOriginalWeight, unsigned if (faceNormals.size() != faces.size()) ComputeNormalFaces(); if (vertexFaces.size() != vertices.size()) - ListIncidenteFaces(); + ListIncidentFaces(); if (faceFaces.size() != faces.size()) - ListIncidenteFaceFaces(); + ListIncidentFaceFaces(); const float cosMaxGradient = COS(FD2R(fMaxGradient)); for (unsigned rep = 0; rep < nIterations; ++rep) { NormalArr newFaceNormals(faceNormals.size()); @@ -560,7 +570,7 @@ unsigned Mesh::FixNonManifold(float magDisplacementDuplicateVertices, VertexIdxA { ASSERT(!vertices.empty() && !faces.empty()); if (vertexFaces.size() != vertices.size()) - ListIncidenteFaces(); + ListIncidentFaces(); // iterate over all vertices and separates the components // incident to the same vertex by duplicating the vertex unsigned numNonManifoldIssues(0); @@ -665,7 +675,10 @@ unsigned Mesh::FixNonManifold(float magDisplacementDuplicateVertices, VertexIdxA } } } - vertexFaces.Release(); + if (numNonManifoldIssues > 0) { + vertexFaces.Release(); + DEBUG_ULTIMATE("Removed %u non-manifold issues", numNonManifoldIssues); + } return numNonManifoldIssues; } /*----------------------------------------------------------------*/ @@ -1484,7 +1497,7 @@ bool Mesh::SavePLY(const String& fileName, const cList& comments, bool b // export texture file name as comment if needed if (HasTexture()) { FOREACH(texId, texturesDiffuse) { - const String textureFileName(Util::getFileFullName(fileName) + std::to_string(texId).c_str() + (bTexLossless?_T(".png"):_T(".jpg"))); + const String textureFileName(Util::getFileFullName(fileName) + std::to_string((unsigned)texId).c_str() + (bTexLossless?_T(".png"):_T(".jpg"))); ply.append_comment((_T("TextureFile ")+Util::getFileNameExt(textureFileName)).c_str()); texturesDiffuse[texId].Save(textureFileName); } @@ -3538,6 +3551,135 @@ void Mesh::CloseHoleQuality(VertexIdxArr& verts) } /*----------------------------------------------------------------*/ + +// remove degenerate faces, with one or more identical vertices or very close vertices (0 - disabled); +// unreferenced vertices and non-manifold edges/vertices can be created, +// so should be followed by RemoveUnreferencedVertices() and FixNonManifold() +Mesh::FIndex Mesh::RemoveDegenerateFaces(Type thArea) { + if (vertexFaces.size() != vertices.size()) + ListIncidentFaces(); + const Type thDoubleAreaSq = SQUARE(thArea * 2); + FaceIdxArr facesRemove; + typedef std::pair Vertex2Vertex; + CLISTDEF0(Vertex2Vertex) vertexPairs; + RFOREACH(idxFace, faces) { + const Face& face = faces[idxFace]; + // check first case when one or more vertices have same index + if (face[0] == face[1] || face[0] == face[2] || face[1] == face[2]) { + // just remove the face + facesRemove.emplace_back(idxFace); + continue; + } + if (thDoubleAreaSq <= 0) + continue; + // check if the face has almost 0 area (see EdgeFunction()) + const Vertex& v0 = vertices[face[0]]; + const Vertex& v1 = vertices[face[1]]; + const Vertex& v2 = vertices[face[2]]; + const Vertex A(v2 - v0); + const Vertex B(v1 - v0); + const Type doubleAreaSq = normSq(B.cross(A)); + if (doubleAreaSq <= thDoubleAreaSq) { + // remove the face + facesRemove.emplace_back(idxFace); + const Type lenghSqA = normSq(A); + const Type lenghSqB = normSq(B); + const Type lenghSqC = normSq(v2 - v1); + // remove two of the vertices, + // moving all adjacent face to the remaining vertex + if (lenghSqA <= thArea && lenghSqB <= thArea) { + vertexPairs.emplace_back(face[2], face[0]); + vertexPairs.emplace_back(face[1], face[0]); + } + else if (lenghSqA <= thArea && lenghSqC <= thArea) { + vertexPairs.emplace_back(face[0], face[2]); + vertexPairs.emplace_back(face[1], face[2]); + } + else if (lenghSqB <= thArea && lenghSqC <= thArea) { + vertexPairs.emplace_back(face[0], face[1]); + vertexPairs.emplace_back(face[2], face[1]); + } else + // remove one of the vertices, + // moving all adjacent face to the closest remaining vertices + if (lenghSqA <= thArea) { + vertexPairs.emplace_back(face[2], face[0]); + } + else if (lenghSqB <= thArea) { + vertexPairs.emplace_back(face[1], face[0]); + } + else if (lenghSqC <= thArea) { + vertexPairs.emplace_back(face[1], face[2]); + } else { + // the vertices are (almost) collinear, remove the smallest edge + if (lenghSqA < lenghSqB) { + if (lenghSqA < lenghSqC) + vertexPairs.emplace_back(face[2], face[0]); + else + vertexPairs.emplace_back(face[2], face[1]); + } else { + if (lenghSqB < lenghSqC) + vertexPairs.emplace_back(face[1], face[0]); + else + vertexPairs.emplace_back(face[2], face[1]); + } + } + } + } + if (facesRemove.empty()) + return 0; + RemoveFaces(facesRemove, true); + if (vertexPairs.empty()) { + DEBUG("Removed %u degenerate faces", facesRemove.size()); + return facesRemove.size(); + } + // replace first vertex with the second + VertexIdxArr mapRemovedVerts(vertices.size()); + mapRemovedVerts.MemsetValue(NO_ID); + const auto TraceMovedVertex = [&mapRemovedVerts](IIndex idx) { + while (mapRemovedVerts[idx] != NO_ID) + idx = mapRemovedVerts[idx]; + return idx; + }; + vertexPairs.RemoveDuplicates(); + RFOREACHPTR(ptrIdxPair, vertexPairs) { + Vertex2Vertex p = *ptrIdxPair; + p.first = TraceMovedVertex(p.first); + p.second = TraceMovedVertex(p.second); + if (p.first == p.second) + continue; + FaceIdxArr& firstVfs = vertexFaces[p.first]; + for (const FIndex idxFace : firstVfs) { + Face& face = faces[idxFace]; + for (VIndex i = 0; i < 3; ++i) + if (face[i] == p.first) + face[i] = p.second; + } + FaceIdxArr& secondVfs = vertexFaces[p.second]; + secondVfs.Join(firstVfs); + secondVfs.RemoveDuplicates(); + firstVfs.Release(); + mapRemovedVerts[p.first] = p.second; + } + const FIndex numRemovedFaces = facesRemove.size() + RemoveDegenerateFaces(0.f); + if (numRemovedFaces > 0) + DEBUG_ULTIMATE("Removed %u zero-area faces", numRemovedFaces); + return numRemovedFaces; +} + +// removing zero-area-faces can generate some new zero-area-faces, +// so iterate till no zero-area faces are encountered or max number of iterations is reached +Mesh::FIndex Mesh::RemoveDegenerateFaces(unsigned maxIterations, Type thArea) { + FIndex totalNumRemovedFaces = 0; + for (unsigned iter=0; iter> mapVertexIndex; + FOREACH(i, vertices) { + const Vertex& vertex = vertices[i]; + const auto ret = mapVertexIndex.emplace(vertex, (VIndex)mapVertexIndex.size()); + if (ret.second) { + // new vertex found + mapVertices[i] = i; + } else { + // duplicate vertex found + mapVertices[i] = ret.first->second; + if (duplicatedVertices) + duplicatedVertices->push_back(i); + } + } + numUniqueVertices = (VIndex)mapVertexIndex.size(); + if (numUniqueVertices == vertices.size()) + return 0; + ReleaseComputable(); + } + // update the vertices and vertexNormals arrays + VertexArr newVertices(0, numUniqueVertices); + VertexArr newVertexNormals; + if (!vertexNormals.empty()) + newVertexNormals.reserve(numUniqueVertices); + FOREACH(i, vertices) { + VIndex& uniqueIndex = mapVertices[i]; + if (uniqueIndex != i) + continue; + uniqueIndex = newVertices.size(); + newVertices.emplace_back(vertices[i]); + if (!vertexNormals.empty()) + newVertexNormals.emplace_back(vertexNormals[i]); + } + vertices = std::move(newVertices); + if (!vertexNormals.empty()) + vertexNormals = std::move(newVertexNormals); + // update the vertex indices in the faces + for (Face& face: faces) { + for (int i = 0; i < 3; ++i) { + face[i] = mapVertices[face[i]]; + ASSERT(face[i] != NO_ID); + } + } + const VIndex numDuplicated(mapVertices.size() - vertices.size()); + DEBUG_ULTIMATE("Removed %u duplicated vertices", numDuplicated); + return numDuplicated; +} + // remove all vertices that are not assigned to any face // (require vertexFaces) Mesh::VIndex Mesh::RemoveUnreferencedVertices(bool bUpdateLists) @@ -3667,6 +3863,7 @@ Mesh::VIndex Mesh::RemoveUnreferencedVertices(bool bUpdateLists) if (vertexRemove.empty()) return 0; RemoveVertices(vertexRemove, bUpdateLists); + DEBUG_ULTIMATE("Removed %u unreferenced vertices", vertexRemove.size()); return vertexRemove.size(); } @@ -4149,7 +4346,7 @@ Mesh Mesh::SubMesh(const FaceIdxArr& chunk) const mesh.faceTexcoords.emplace_back(tri[i]); } } - mesh.ListIncidenteFaces(); + mesh.ListIncidentFaces(); mesh.RemoveUnreferencedVertices(); mesh.FixNonManifold(); return mesh; @@ -4239,7 +4436,7 @@ bool Mesh::TransferTexture(Mesh& mesh, const FaceIdxArr& faceSubsetIndices, unsi // the two meshes are different, transfer the texture by finding the closest point // on the two surfaces if (vertexFaces.size() != vertices.size()) - ListIncidenteFaces(); + ListIncidentFaces(); if (mesh.vertexNormals.size() != mesh.vertices.size()) mesh.ComputeNormalVertices(); #if USE_MESH_INT == USE_MESH_BVH diff --git a/libs/MVS/Mesh.h b/libs/MVS/Mesh.h index 8e6df53f0..9e54ff806 100644 --- a/libs/MVS/Mesh.h +++ b/libs/MVS/Mesh.h @@ -149,9 +149,10 @@ class MVS_API Mesh void Release(); void ReleaseExtra(); + void ReleaseComputable(); void EmptyExtra(); - void Swap(Mesh&); - void Join(const Mesh&); + Mesh& Swap(Mesh&); + Mesh& Join(const Mesh&); bool IsEmpty() const { return vertices.empty(); } bool IsWatertight(); bool HasTexture() const { return HasTextureCoordinates() && !texturesDiffuse.empty(); } @@ -162,9 +163,9 @@ class MVS_API Mesh Box GetAABB(const Box& bound) const; Vertex GetCenter() const; - void ListIncidenteVertices(); - void ListIncidenteFaces(); - void ListIncidenteFaceFaces(); + void ListIncidentVertices(); + void ListIncidentFaces(); + void ListIncidentFaceFaces(); void ListBoundaryVertices(); void ComputeNormalFaces(); void ComputeNormalVertices(); @@ -179,7 +180,7 @@ class MVS_API Mesh void GetAdjVertices(VIndex, VertexIdxArr&) const; void GetAdjVertexFaces(VIndex, VIndex, FaceIdxArr&) const; - unsigned FixNonManifold(float magDisplacementDuplicateVertices = 0.01f, VertexIdxArr* duplicatedVertices = NULL); + unsigned FixNonManifold(float magDisplacementDuplicateVertices=0.01f, VertexIdxArr* duplicatedVertices=NULL); void Clean(float fDecimate=0.7f, float fSpurious=10.f, bool bRemoveSpikes=true, unsigned nCloseHoles=30, unsigned nSmoothMesh=2, float fEdgeLength=0, bool bLastClean=true); void EnsureEdgeSize(float minEdge=-0.5f, float maxEdge=-4.f, float collapseRatio=0.2, float degenerate_angle_deg=150, int mode=1, int max_iters=50); @@ -189,9 +190,12 @@ class MVS_API Mesh void Decimate(VertexIdxArr& verticesRemove); void CloseHole(VertexIdxArr& vertsLoop); void CloseHoleQuality(VertexIdxArr& vertsLoop); + FIndex RemoveDegenerateFaces(Type thArea=1e-5f); + FIndex RemoveDegenerateFaces(unsigned maxIterations, Type thArea=1e-5f); void RemoveFacesOutside(const OBB3f&); void RemoveFaces(FaceIdxArr& facesRemove, bool bUpdateLists=false); void RemoveVertices(VertexIdxArr& vertexRemove, bool bUpdateLists=false); + VIndex RemoveDuplicatedVertices(VertexIdxArr* duplicatedVertices=NULL); VIndex RemoveUnreferencedVertices(bool bUpdateLists=false); std::vector SplitMeshPerTextureBlob() const; void ConvertTexturePerVertex(Mesh&) const; diff --git a/libs/MVS/SceneRefine.cpp b/libs/MVS/SceneRefine.cpp index 8628351dd..c7ae91790 100644 --- a/libs/MVS/SceneRefine.cpp +++ b/libs/MVS/SceneRefine.cpp @@ -399,11 +399,11 @@ bool MeshRefine::InitImages(Real scale, Real sigma) void MeshRefine::ListVertexFacesPre() { scene.mesh.EmptyExtra(); - scene.mesh.ListIncidenteFaces(); + scene.mesh.ListIncidentFaces(); } void MeshRefine::ListVertexFacesPost() { - scene.mesh.ListIncidenteVertices(); + scene.mesh.ListIncidentVertices(); scene.mesh.ListBoundaryVertices(); } diff --git a/libs/MVS/SceneRefineCUDA.cpp b/libs/MVS/SceneRefineCUDA.cpp index a8c671e4e..4e56af3bf 100644 --- a/libs/MVS/SceneRefineCUDA.cpp +++ b/libs/MVS/SceneRefineCUDA.cpp @@ -2259,12 +2259,12 @@ bool MeshRefineCUDA::InitImages(float scale, float sigma) void MeshRefineCUDA::ListVertexFacesPre() { scene.mesh.EmptyExtra(); - scene.mesh.ListIncidenteFaces(); + scene.mesh.ListIncidentFaces(); reportCudaError(faces.Reset(scene.mesh.faces)); } void MeshRefineCUDA::ListVertexFacesPost() { - scene.mesh.ListIncidenteVertices(); + scene.mesh.ListIncidentVertices(); scene.mesh.ListBoundaryVertices(); ASSERT(!scene.mesh.vertices.IsEmpty() && scene.mesh.vertices.GetSize() == scene.mesh.vertexVertices.GetSize()); // set vertex vertices diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 0feb26996..4b3c63e04 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -409,9 +409,9 @@ MeshTexture::~MeshTexture() void MeshTexture::ListVertexFaces() { scene.mesh.EmptyExtra(); - scene.mesh.ListIncidenteFaces(); + scene.mesh.ListIncidentFaces(); scene.mesh.ListBoundaryVertices(); - scene.mesh.ListIncidenteFaceFaces(); + scene.mesh.ListIncidentFaceFaces(); } // extract array of faces viewed by each image @@ -1660,7 +1660,7 @@ void MeshTexture::ProcessMask(Image8U& mask, int stripWidth) // compute the set of valid pixels at the border of the texture patch #define ISEMPTY(mask, x,y) (mask(y,x) == empty) const int width(mask.width()), height(mask.height()); - typedef std::unordered_set PixelSet; + typedef std::unordered_set> PixelSet; PixelSet borderPixels; for (int y=0; y