/** @file MeshAlgAdjacency.cpp @maintainer Morgan McGuire, matrix@graphics3d.com @created 2003-09-14 @edited 2005-02-24 Copyright 2000-2003, Morgan McGuire. All rights reserved. */ #include "G3D/Table.h" #include "G3D/MeshAlg.h" #include "G3D/Set.h" namespace G3D { /** A half [i.e. directed] edge. */ class MeshDirectedEdgeKey { public: /** vertexIndex[0] <= vertexIndex[1] */ int vertexIndex[2]; MeshDirectedEdgeKey() {} MeshDirectedEdgeKey( const int i0, const int i1) { if (i0 <= i1) { vertexIndex[0] = i0; vertexIndex[1] = i1; } else { vertexIndex[0] = i1; vertexIndex[1] = i0; } } bool operator==(const MeshDirectedEdgeKey& e2) const { for (int i = 0; i < 2; ++i) { if (vertexIndex[i] != e2.vertexIndex[i]) { return false; } } return true; } }; } template<> struct HashTrait { static size_t hashCode(const G3D::MeshDirectedEdgeKey& key) { return key.vertexIndex[0] + (key.vertexIndex[1] << 16); } }; namespace G3D { /** A hashtable mapping edges to lists of indices for faces. This is used instead of Table because of the special logic in insert. Used only for MeshAlg::computeAdjacency. In the face lists, index f >= 0 indicates that f contains the edge as a forward edge. Index f < 0 indicates that ~f contains the edge as a backward edge. */ class MeshEdgeTable { public: typedef Table > ET; private: ET table; public: /** Clears the table. */ void clear() { table.clear(); } /** Inserts the faceIndex into the edge's face list. The index may be a negative number indicating a backface. */ void insert(const MeshDirectedEdgeKey& edge, int faceIndex) { // debugAssertM((table.size() > 20) && (table.debugGetLoad() < 0.5 || table.debugGetNumBuckets() < 20), // "MeshEdgeTable is using a poor hash function."); if (! table.containsKey(edge)) { // First time Array x(1); x[0] = faceIndex; table.set(edge, x); } else { table[edge].append(faceIndex); } } /** Returns the face list for a given edge */ const Array& get(const MeshDirectedEdgeKey& edge) { return table[edge]; } ET::Iterator begin() { return table.begin(); } const ET::Iterator end() const { return table.end(); } }; /** edgeTable[edgeKey] is a list of faces containing Used and cleared by MeshModel::computeAdjacency() */ static MeshEdgeTable edgeTable; /** Assigns the edge index into the next unassigned edge index. The edge index may be negative, indicating a reverse edge. */ static void assignEdgeIndex(MeshAlg::Face& face, int e) { for (int i = 0; i < 3; ++i) { if (face.edgeIndex[i] == MeshAlg::Face::NONE) { face.edgeIndex[i] = e; return; } } debugAssertM(false, "Face has already been assigned 3 edges"); } void MeshAlg::computeAdjacency( const Array& vertexGeometry, const Array& indexArray, Array& faceArray, Array& edgeArray, Array< Array >& adjacentFaceArray) { Array vertexArray; computeAdjacency(vertexGeometry, indexArray, faceArray, edgeArray, vertexArray); // Convert the vertexArray into adjacentFaceArray adjacentFaceArray.clear(); adjacentFaceArray.resize(vertexArray.size()); for (int v = 0; v < adjacentFaceArray.size(); ++v) { adjacentFaceArray[v] = vertexArray[v].faceIndex; } } void MeshAlg::computeAdjacency( const Array& vertexGeometry, const Array& indexArray, Array& faceArray, Array& edgeArray, Array& vertexArray) { edgeArray.clear(); vertexArray.clear(); faceArray.clear(); edgeTable.clear(); // Face normals Array faceNormal; // This array has the same size as the vertex array vertexArray.resize(vertexGeometry.size()); // Iterate through the triangle list for (int q = 0; q < indexArray.size(); q += 3) { Vector3 vertex[3]; int f = faceArray.size(); MeshAlg::Face& face = faceArray.next(); // Construct the face for (int j = 0; j < 3; ++j) { int v = indexArray[q + j]; face.vertexIndex[j] = v; face.edgeIndex[j] = Face::NONE; // Store back pointers in the vertices vertexArray[v].faceIndex.append(f); // We'll need these vertices to find the face normal vertex[j] = vertexGeometry[v]; } // Compute the face normal Vector3 N = (vertex[1] - vertex[0]).cross(vertex[2] - vertex[0]); faceNormal.append(N.directionOrZero()); static const int nextIndex[] = {1, 2, 0}; // Add each edge to the edge table. for (int j = 0; j < 3; ++j) { const int i0 = indexArray[q + j]; const int i1 = indexArray[q + nextIndex[j]]; const MeshDirectedEdgeKey edge(i0, i1); if (i0 == edge.vertexIndex[0]) { // The edge was directed in the same manner as in the face edgeTable.insert(edge, f); } else { // The edge was directed in the opposite manner as in the face edgeTable.insert(edge, ~f); } } } // For each edge in the edge table, create an edge in the edge array. // Collapse every 2 edges from adjacent faces. MeshEdgeTable::ET::Iterator cur = edgeTable.begin(); MeshEdgeTable::ET::Iterator end = edgeTable.end(); Array tempEdgeArray; while (cur != end) { MeshDirectedEdgeKey& edgeKey = cur->key; Array& faceIndexArray = cur->value; // Process this edge while (faceIndexArray.size() > 0) { // Remove the last index int f0 = faceIndexArray.pop(); // Find the normal to that face const Vector3& n0 = faceNormal[(f0 >= 0) ? f0 : ~f0]; bool found = false; // We try to find the matching face with the closest // normal. This ensures that we don't introduce a lot // of artificial ridges into flat parts of a mesh. double ndotn = -2; int f1 = -1, i1 = -1; // Try to Find the face with the matching edge for (int i = faceIndexArray.size() - 1; i >= 0; --i) { int f = faceIndexArray[i]; if ((f >= 0) != (f0 >= 0)) { // This face contains the oppositely oriented edge // and has not been assigned too many edges const Vector3& n1 = faceNormal[(f >= 0) ? f : ~f]; double d = n1.dot(n0); if (found) { // We previously found a good face; see if this // one is better. if (d > ndotn) { // This face is better. ndotn = d; f1 = f; i1 = i; } } else { // This is the first face we've found found = true; ndotn = d; f1 = f; i1 = i; } } } // Create the new edge int e = tempEdgeArray.size(); Edge& edge = tempEdgeArray.next(); edge.vertexIndex[0] = edgeKey.vertexIndex[0]; edge.vertexIndex[1] = edgeKey.vertexIndex[1]; if (f0 >= 0) { edge.faceIndex[0] = f0; edge.faceIndex[1] = Face::NONE; assignEdgeIndex(faceArray[f0], e); } else { // The face indices above are two's complemented. // this code restores them to regular indices. debugAssert((~f0) >= 0); edge.faceIndex[1] = ~f0; edge.faceIndex[0] = Face::NONE; // The edge index *does* need to be inverted, however. assignEdgeIndex(faceArray[~f0], ~e); } if (found) { // We found a matching face; remove both // faces from the active list. faceIndexArray.fastRemove(i1); if (f1 >= 0) { edge.faceIndex[0] = f1; assignEdgeIndex(faceArray[f1], e); } else { edge.faceIndex[1] = ~f1; assignEdgeIndex(faceArray[~f1], ~e); } } } ++cur; } edgeTable.clear(); // Move boundary edges to the end of the list and then // clean up the face references into them { // Map old edge indices to new edge indices Array newIndex(tempEdgeArray.size()); // Index of the start and end of the edge array int i = 0; int j = tempEdgeArray.size() - 1; edgeArray.resize(tempEdgeArray.size()); for (int e = 0; e < tempEdgeArray.size(); ++e) { if (tempEdgeArray[e].boundary()) { newIndex[e] = j; --j; } else { newIndex[e] = i; ++i; } edgeArray[newIndex[e]] = tempEdgeArray[e]; } debugAssertM(i == j + 1, "Counting from front and back of array did not match"); // Fix the faces for (int f = 0; f < faceArray.size(); ++f) { Face& face = faceArray[f]; for (int q = 0; q < 3; ++q) { int e = face.edgeIndex[q]; if (e < 0) { // Backwards edge; twiddle before and after conversion face.edgeIndex[q] = ~newIndex[~e]; } else { // Regular edge; remap the index face.edgeIndex[q] = newIndex[e]; } } } } // Now order the edge indices inside the faces correctly. for (int f = 0; f < faceArray.size(); ++f) { Face& face = faceArray[f]; int e0 = face.edgeIndex[0]; int e1 = face.edgeIndex[1]; int e2 = face.edgeIndex[2]; // e0 will always remain first. The only // question is whether e1 and e2 should be swapped. // See if e1 begins at the vertex where e1 ends. const int e0End = (e0 < 0) ? edgeArray[~e0].vertexIndex[0] : edgeArray[e0].vertexIndex[1]; const int e1Begin = (e1 < 0) ? edgeArray[~e1].vertexIndex[1] : edgeArray[e1].vertexIndex[0]; if (e0End != e1Begin) { // We must swap e1 and e2 face.edgeIndex[1] = e2; face.edgeIndex[2] = e1; } } // Fill out the edge adjacency information in the vertex array for (int e = 0; e < edgeArray.size(); ++e) { const Edge& edge = edgeArray[e]; vertexArray[edge.vertexIndex[0]].edgeIndex.append(e); vertexArray[edge.vertexIndex[1]].edgeIndex.append(~e); } } void MeshAlg::weldBoundaryEdges( Array& faceArray, Array& edgeArray, Array& vertexArray) { // Copy over the original edge array Array oldEdgeArray = edgeArray; // newEdgeIndex[e] is the new index of the old edge with index e // Note that newEdgeIndex[e] might be negative, indicating that // the edge switched direction between the arrays. Array newEdgeIndex(edgeArray.size()); edgeArray.resize(0); // boundaryEdgeIndices[v_low] is an array of the indices of // all boundary edges whose lower vertex is v_low. Table > boundaryEdgeIndices; // Copy over non-boundary edges to the new array for (int e = 0; e < oldEdgeArray.size(); ++e) { if (oldEdgeArray[e].boundary()) { // Add to the boundary table const int v_low = iMin(oldEdgeArray[e].vertexIndex[0], oldEdgeArray[e].vertexIndex[1]); if (! boundaryEdgeIndices.containsKey(v_low)) { boundaryEdgeIndices.set(v_low, Array()); } boundaryEdgeIndices[v_low].append(e); // We'll fill out newEdgeIndex[e] later, when we find pairs } else { // Copy the edge to the new array newEdgeIndex[e] = edgeArray.size(); edgeArray.append(oldEdgeArray[e]); } } // Remove all edges from the table that have pairs. Table >::Iterator cur = boundaryEdgeIndices.begin(); Table >::Iterator end = boundaryEdgeIndices.end(); while (cur != end) { Array& boundaryEdge = cur->value; for (int i = 0; i < boundaryEdge.size(); ++i) { int ei = boundaryEdge[i]; const Edge& edgei = oldEdgeArray[ei]; for (int j = i + 1; j < boundaryEdge.size(); ++j) { int ej = boundaryEdge[j]; const Edge& edgej = oldEdgeArray[ej]; // See if edge ei is the reverse (match) of edge ej. // True if the edges match bool match = false; // True if edgej's vertex indices are reversed from // edgei's (usually true). bool reversej = false; int u = edgei.vertexIndex[0]; int v = edgei.vertexIndex[1]; if (edgei.faceIndex[0] != Face::NONE) { // verts|faces // edgei = [u v A /] if (edgej.faceIndex[0] != Face::NONE) { if ((edgej.vertexIndex[0] == v) && (edgej.vertexIndex[1] == u)) { // This is the most common of the four cases // edgej = [v u B /] match = true; reversej = true; } } else { if ((edgej.vertexIndex[0] == u) && (edgej.vertexIndex[1] == v)) { // edgej = [u v / B] match = true; } } } else { // edgei = [u v / A] if (edgej.faceIndex[0] != Face::NONE) { if ((edgej.vertexIndex[0] == u) && (edgej.vertexIndex[1] == v)) { // edgej = [u v B /] match = true; } } else { if ((edgej.vertexIndex[0] == v) && (edgej.vertexIndex[1] == u)) { // edgej = [v u / B] match = true; reversej = true; } } } if (match) { // ei and ej can be paired as a single edge int e = edgeArray.size(); Edge& edge = edgeArray.next(); // Follow the direction of edgei. edge = edgei; newEdgeIndex[ei] = e; // Insert the face index for edgej. int fj = edgej.faceIndex[0]; if (fj == Face::NONE) { fj = edgej.faceIndex[1]; } if (edge.faceIndex[0] == Face::NONE) { edge.faceIndex[0] = fj; } else { edge.faceIndex[1] = fj; } if (reversej) { // The new edge is backwards of the old edge for ej newEdgeIndex[ej] = ~e; } else { newEdgeIndex[ej] = e; } // Remove both ei and ej from being candidates for future pairing. // Remove ej first since it comes later in the list (removing // ei would decrease the index of ej to j - 1). boundaryEdge.fastRemove(j); boundaryEdge.fastRemove(i); // Re-process element i, which is now a new edge index --i; // Jump out of the j for-loop break; } } } ++cur; } // Anything remaining in the table is a real boundary edge; just copy it to // the end of the array. cur = boundaryEdgeIndices.begin(); end = boundaryEdgeIndices.end(); while (cur != end) { Array& boundaryEdge = cur->value; for (int b = 0; b < boundaryEdge.size(); ++b) { const int e = boundaryEdge[b]; newEdgeIndex[e] = edgeArray.size(); edgeArray.append(oldEdgeArray[e]); } ++cur; } // Finally, fix up edge indices in the face and vertex arrays for (int f = 0; f < faceArray.size(); ++f) { Face& face = faceArray[f]; for (int i = 0; i < 3; ++i) { int e = face.edgeIndex[i]; if (e < 0) { face.edgeIndex[i] = ~newEdgeIndex[~e]; } else { face.edgeIndex[i] = newEdgeIndex[e]; } } } for (int v = 0; v < vertexArray.size(); ++v) { Vertex& vertex = vertexArray[v]; for (int i = 0; i < vertex.edgeIndex.size(); ++i) { int e = vertex.edgeIndex[i]; if (e < 0) { vertex.edgeIndex[i] = ~newEdgeIndex[~e]; } else { vertex.edgeIndex[i] = newEdgeIndex[e]; } } } } void MeshAlg::weldAdjacency( const Array& originalGeometry, Array& faceArray, Array& edgeArray, Array& vertexArray, double radius) { // Num vertices const int n = originalGeometry.size(); // canonical[v] = first occurance of any vertex near oldVertexArray[v] Array canonical(n); Array toNew, toOld; // Throw away the new vertex array Array dummy; computeWeld(originalGeometry, dummy, toNew, toOld, radius); for (int v = 0; v < canonical.size(); ++v) { // Round-trip through the toNew/toOld process. This will give // us the original vertex. canonical[v] = toOld[toNew[v]]; } // Destroy vertexArray (we reconstruct it below) vertexArray.clear(); vertexArray.resize(n); bool hasBoundaryEdges = false; // Fix edge vertex indices for (int e = 0; e < edgeArray.size(); ++e) { Edge& edge = edgeArray[e]; const int v0 = canonical[edge.vertexIndex[0]]; const int v1 = canonical[edge.vertexIndex[1]]; edge.vertexIndex[0] = v0; edge.vertexIndex[1] = v1; vertexArray[v0].edgeIndex.append(e); vertexArray[v1].edgeIndex.append(~e); hasBoundaryEdges = hasBoundaryEdges || edge.boundary(); } // Fix face vertex indices for (int f = 0; f < faceArray.size(); ++f) { Face& face = faceArray[f]; for (int i = 0; i < 3; ++i) { const int v = canonical[face.vertexIndex[i]]; face.vertexIndex[i] = v; // Add the back pointer vertexArray[v].faceIndex.append(f); } } if (hasBoundaryEdges) { // As a result of the welding, some of the boundary edges at // the end of the array may now have mates and no longer be // boundaries. Try to pair these up. weldBoundaryEdges(faceArray, edgeArray, vertexArray); } } void MeshAlg::debugCheckConsistency( const Array& faceArray, const Array& edgeArray, const Array& vertexArray) { #ifdef _DEBUG for (int v = 0; v < vertexArray.size(); ++v) { const MeshAlg::Vertex& vertex = vertexArray[v]; for (int i = 0; i < vertex.edgeIndex.size(); ++i) { const int e = vertex.edgeIndex[i]; debugAssert(edgeArray[(e >= 0) ? e : ~e].containsVertex(v)); } for (int i = 0; i < vertex.faceIndex.size(); ++i) { const int f = vertex.faceIndex[i]; debugAssert(faceArray[f].containsVertex(v)); } } for (int e = 0; e < edgeArray.size(); ++e) { const MeshAlg::Edge& edge = edgeArray[e]; for (int i = 0; i < 2; ++i) { debugAssert((edge.faceIndex[i] == MeshAlg::Face::NONE) || faceArray[edge.faceIndex[i]].containsEdge(e)); debugAssert(vertexArray[edge.vertexIndex[i]].inEdge(e)); } } // Every face's edge must be on that face for (int f = 0; f < faceArray.size(); ++f) { const MeshAlg::Face& face = faceArray[f]; for (int i = 0; i < 3; ++i) { int e = face.edgeIndex[i]; int ei = (e >= 0) ? e : ~e; debugAssert(edgeArray[ei].inFace(f)); // Make sure the edge is oriented appropriately if (e >= 0) { debugAssert(edgeArray[ei].faceIndex[0] == (int)f); } else { debugAssert(edgeArray[ei].faceIndex[1] == (int)f); } debugAssert(vertexArray[face.vertexIndex[i]].inFace(f)); } } #else (void)faceArray; (void)edgeArray; (void)vertexArray; #endif // _DEBUG } } // G3D namespace