2021SC@SDUSC
An important feature of Dust3D is "seamless modeling". When the model spacing is less than a certain value, the automatic mesh stitching between models is realized. This process involves the relevant knowledge of half edge structure.
half-edge structure
Half edge data structure is a data structure based on grid edges. In this data structure, we believe that each edge of the grid is divided into two opposite sides.
convertHalfEdgesToEdgeLoops()
convertHalfEdgesToEdgeLoops() in the MeshRecombiner class converts a half edge structure into a list of circular edges. First, store each half edge in the vertexLinkMap, and then traverse backward from the first vertex. When the number of elements does not exceed the sequence capacity limit, add vertices to edgeLoop until a closed loop is formed.
bool MeshRecombiner::convertHalfEdgesToEdgeLoops(const std::vector<std::pair<size_t, size_t>> &halfEdges, std::vector<std::vector<size_t>> *edgeLoops) { std::map<size_t, size_t> vertexLinkMap; for (const auto &halfEdge: halfEdges) { auto inserResult = vertexLinkMap.insert(halfEdge); if (!inserResult.second) { return false; } } while (!vertexLinkMap.empty()) { std::vector<size_t> edgeLoop; //Find the first value of the first node in the map size_t vertex = vertexLinkMap.begin()->first; size_t head = vertex; bool loopBack = false; size_t limitLoop = MAX_EDGE_LOOP_LENGTH; //When the number of elements does not exceed the sequence capacity limit, a new edge is added to edgeLoop while ((limitLoop--) > 0) { edgeLoop.push_back(vertex); //Adds the current vertex to the edge loop auto findNext = vertexLinkMap.find(vertex); //If the current node is the last node of the map, a new loop is started if (findNext == vertexLinkMap.end()) break; //If the second value of the current node is the head node of the map, the closed-loop edge sequence is successfully generated vertex = findNext->second; if (vertex == head) { loopBack = true; break; } } //Failed to generate closed loop if (!loopBack) { return false; } //The number of edges in edgeLoop is less than 3 and cannot form a closed loop if (edgeLoop.size() < 3) { return false; } for (const auto &vertex: edgeLoop) { vertexLinkMap.erase(vertex); } edgeLoops->push_back(edgeLoop); } return true; }
recombine()
First, create a face map for the half edge, and then find the position where there is a gap:
Traverse each face, and read the vertex index sequence of the face with index. If the first node value of the current node is empty, check the next adjacent face. If the first node value is also empty, there is a gap in the current position.
std::map<size_t, std::vector<size_t>> seamLink; for (const auto &face: *m_faces) { for (size_t i = 0; i < face.size(); ++i) { const auto &index = face[i]; auto source = (*m_verticesSourceIndices)[index]; //Face vertex index sequence if (MeshCombiner::Source::None == source.first) { //If the value of the first node is None, the next adjacent face is checked auto next = face[(i + 1) % face.size()]; auto nextSource = (*m_verticesSourceIndices)[next]; if (MeshCombiner::Source::None == nextSource.first) { //There is a gap between index and next seamLink[index].push_back(next); } } } }
The vertices at the gap are separated one by one as independent individuals_ For each face of faces: read its vertex index sequence. If the first node of the sequence vertex is empty, find the corresponding vertex in islandmap. If it is not the tail node of map, it indicates that the islandmap contains the vertex at the gap, in M_ The area where faces is located increases the connection between the face and the island, and regenerates the edges.
std::map<size_t, size_t> seamVertexToIslandMap; //Separate the vertices at the gap as independent individuals size_t islands = splitSeamVerticesToIslands(seamLink, &seamVertexToIslandMap); std::map<std::pair<size_t, size_t>, std::pair<size_t, bool>> edgesInSeamArea; for (size_t faceIndex = 0; faceIndex < (*m_faces).size(); ++faceIndex) { const auto &face = (*m_faces)[faceIndex]; bool containsSeamVertex = false; bool inFirstGroup = false; size_t island = 0; for (size_t i = 0; i < face.size(); ++i) { const auto &index = face[i]; auto source = (*m_verticesSourceIndices)[index]; if (MeshCombiner::Source::None == source.first) { //If the first value of sequence vertex pair is null, find the corresponding vertex in seamVertexToIslandMap const auto &findIsland = seamVertexToIslandMap.find(index); //If the vertex is not the tail node of seamVertexToIslandMap, the island map contains the vertex at the gap if (findIsland != seamVertexToIslandMap.end()) { containsSeamVertex = true; island = findIsland->second; } } else if (MeshCombiner::Source::First == source.first) { inFirstGroup = true; } } if (containsSeamVertex) { //In M_ The area where faces is located adds the face combined with the island m_facesInSeamArea.insert({faceIndex, island}); //Regenerate edges for the combination for (size_t i = 0; i < face.size(); ++i) { const auto &index = face[i]; const auto &next = face[(i + 1) % face.size()]; std::pair<size_t, size_t> edge = {index, next}; edgesInSeamArea.insert({edge, {island, inFirstGroup}}); } } }
For islandsMap, you need to adjust its triangular mesh
for (auto &it: islandsMap) { for (size_t side = 0; side < 2; ++side) { for (size_t i = 0; i < it.second.edgeLoops[side].size(); ++i) { auto &edgeLoop = it.second.edgeLoops[side][i]; size_t totalAdjustedTriangles = 0; size_t adjustedTriangles = 0; while ((adjustedTriangles=adjustTrianglesFromSeam(edgeLoop, it.first)) > 0) { totalAdjustedTriangles += adjustedTriangles; } } } }
adjustTrianglesFromSeam()
After mesh stitching, relevant duplicate information needs to be deleted and adjusted.
size_t MeshRecombiner::adjustTrianglesFromSeam(std::vector<size_t> &edgeLoop, size_t seamIndex) { if (edgeLoop.size() <= 3) return 0; std::vector<size_t> halfEdgeToFaces; for (size_t i = 0; i < edgeLoop.size(); ++i) { size_t j = (i + 1) % edgeLoop.size(); //Find the face it represents for half (j, i) auto findFace = m_halfEdgeToFaceMap.find({edgeLoop[j], edgeLoop[i]}); if (findFace == m_halfEdgeToFaceMap.end()) { qDebug() << "Find face for half edge failed:" << edgeLoop[j] << edgeLoop[i]; return 0; } //Stores the second vertex of the current half in the sequence halfEdgeToFaces.push_back(findFace->second); } std::vector<size_t> removedFaceIndices; std::set<size_t> ignored; for (size_t i = 0; i < edgeLoop.size(); ++i) { size_t j = (i + 1) % edgeLoop.size(); //When an adjacent half represents the same face, one edge is deleted in the sequence index and the other half is recorded if (halfEdgeToFaces[i] == halfEdgeToFaces[j]) { removedFaceIndices.push_back(halfEdgeToFaces[i]); ignored.insert(edgeLoop[j]); ++i; continue; } } //When there is a deletion operation, a new cyclic edge sequence is generated if (!ignored.empty()) { std::vector<size_t> newEdgeLoop; for (const auto &v: edgeLoop) { if (ignored.find(v) != ignored.end()) continue; newEdgeLoop.push_back(v); } if (newEdgeLoop.size() < 3) return 0; edgeLoop = newEdgeLoop; for (const auto &faceIndex: removedFaceIndices) m_facesInSeamArea.insert({faceIndex, seamIndex}); } return ignored.size(); }
If there is any mistake, please correct it.