diff --git a/pxr/usd/usdGeom/tetMesh.cpp b/pxr/usd/usdGeom/tetMesh.cpp index 3c07c7b2c7..ab9203f4a4 100644 --- a/pxr/usd/usdGeom/tetMesh.cpp +++ b/pxr/usd/usdGeom/tetMesh.cpp @@ -180,65 +180,139 @@ PXR_NAMESPACE_CLOSE_SCOPE // --(BEGIN CUSTOM CODE)-- PXR_NAMESPACE_OPEN_SCOPE -bool UsdGeomTetMesh::ComputeSurfaceFaces(const UsdGeomTetMesh& tetMesh, - VtVec3iArray* surfaceFaceIndices, - const UsdTimeCode timeCode) +namespace { + +GfVec3i +_Sorted(GfVec3i v) { - - if (surfaceFaceIndices == nullptr) + if (v[0] > v[1]) { + std::swap(v[0], v[1]); + } + if (v[0] > v[2]) { + std::swap(v[0], v[2]); + } + if (v[1] > v[2]) { + std::swap(v[1], v[2]); + } + return v; +} + +struct _Vec3iHash +{ + size_t operator()(const GfVec3i& v) const { - return false; + return + static_cast(v[0]) << 42 ^ + static_cast(v[1]) << 21 ^ + static_cast(v[2]); } - - // The four triangles of the tetrahedron - static int tetFaceIndices[4][3] = { - {1,2,3}, - {0,3,2}, - {0,1,3}, - {0,2,1} - }; +}; - const UsdAttribute& tetVertexIndicesAttr = tetMesh.GetTetVertexIndicesAttr(); - VtVec4iArray tetVertexIndices; - tetVertexIndicesAttr.Get(&tetVertexIndices, timeCode); +struct _Vec3iCmp +{ + // Comparator function + bool operator()(const GfVec3i& f1, const GfVec3i &f2) + { + if (f1[0] == f2[0]) + { + if (f1[1] == f2[1]) + { + return f1[2] < f2[2]; + } + + return f1[1] < f2[1]; + } + + return f1[0] < f2[0]; + } +}; + +VtVec3iArray +_ComputeSurfaceFaces(const VtVec4iArray &tetVertexIndices) +{ // The surface faces are made of triangles that are not shared between - // tetrahedra and only occur once. We create a hashmap from face - // triangles to occurence counts and then run through all the triangles - // in the tetmesh incrementing the count. When we are done, we sweep - // the hashmap and gather the faces with one occurence. - TfHashMap<_IndexTri, size_t, - _IndexTriHash, _IndexTriEquals> triangleCounts; + // tetrahedra and only occur once. We use a hashmap from triangles + // to counting information to see whether a triangle occurs in the + // hashmap just and thus is not shared. We then sweep the hashmap + // to find all triangles. + // + // Recall that a triangle is a triple of indices. But two triangles are + // shared if these two triples are related by a permutation. Thus, the + // key into the hashmap is the sorted triple which we call the signature. + // + // The value of the hashmap is a pair of (count, triple). The triple + // is stored next to the count so that we do not loose the orientation + // information that was lost when sorting the triple. + // + using SigToCountAndTriangle = + TfHashMap, _Vec3iHash>; + + SigToCountAndTriangle sigToCountAndTriangle; for (size_t t = 0; t < tetVertexIndices.size(); t++) { const GfVec4i& tet = tetVertexIndices[t]; + + // The four triangles of a tetrahedron + static int tetFaceIndices[4][3] = { + {1,2,3}, + {0,3,2}, + {0,1,3}, + {0,2,1} + }; for (int tFace = 0; tFace < 4; tFace++) { - - const _IndexTri faceId(tet[tetFaceIndices[tFace][0]], - tet[tetFaceIndices[tFace][1]], - tet[tetFaceIndices[tFace][2]]); - triangleCounts[faceId]++; + + // A triangle of this tetrahedron. + const GfVec3i triangle( + tet[tetFaceIndices[tFace][0]], + tet[tetFaceIndices[tFace][1]], + tet[tetFaceIndices[tFace][2]]); + + std::pair &item = + sigToCountAndTriangle[_Sorted(triangle)]; + item.first++; + item.second = triangle; } } - - // Take a guess and generously reserve one surface face - // per tetrahedron. - surfaceFaceIndices->reserve(tetVertexIndices.size()); - - for(auto&& [first, second] : triangleCounts) { - if (second == 1) { - const _IndexTri& tri = first; - surfaceFaceIndices->push_back(tri.GetUnsortedIndices()); + + VtVec3iArray result; + // Reserve one surface face per tetrahedron. + // A tetrahedron can contribute up to 4 faces, but typically, + // most faces of tet mesh are shared. So this is really just + // a guess. + result.reserve(tetVertexIndices.size()); + + for(auto && [sig, countAndTriangle] : sigToCountAndTriangle) { + if (countAndTriangle.first == 1) { + result.push_back(countAndTriangle.second); } } // Need to sort results for deterministic behavior across different // compiler/OS versions - FaceVertexIndicesCompare comparator; - std::sort(surfaceFaceIndices->begin(), - surfaceFaceIndices->end(), comparator); + std::sort(result.begin(), result.end(), _Vec3iCmp()); + + return result; +} +} + +bool UsdGeomTetMesh::ComputeSurfaceFaces(const UsdGeomTetMesh& tetMesh, + VtVec3iArray* surfaceFaceIndices, + const UsdTimeCode timeCode) +{ + + if (surfaceFaceIndices == nullptr) + { + return false; + } + + const UsdAttribute& tetVertexIndicesAttr = tetMesh.GetTetVertexIndicesAttr(); + VtVec4iArray tetVertexIndices; + tetVertexIndicesAttr.Get(&tetVertexIndices, timeCode); + + *surfaceFaceIndices = _ComputeSurfaceFaces(tetVertexIndices); return true; } diff --git a/pxr/usd/usdGeom/tetMesh.h b/pxr/usd/usdGeom/tetMesh.h index 11079f835b..6f3dc56acf 100644 --- a/pxr/usd/usdGeom/tetMesh.h +++ b/pxr/usd/usdGeom/tetMesh.h @@ -241,106 +241,6 @@ class UsdGeomTetMesh : public UsdGeomPointBased static bool ComputeSurfaceFaces(const UsdGeomTetMesh& tetMesh, VtVec3iArray* surfaceFaceIndices, const UsdTimeCode timeCode = UsdTimeCode::Default()); - -private: - // IndexTri is a helper class used by ComputeSurfaceFaces - class _IndexTri - { - public: - _IndexTri(int fv0, int fv1, int fv2):_unsortedIndices(fv0, fv1, fv2), - _sortedIdx0(fv0), - _sortedIdx1(fv1), - _sortedIdx2(fv2) - { - // _sortedIdx0, _sortedIdx1 and _sortedIdx2 are - // sorted indices because order is important when determining - // face equality. - _SortFVIndices(_sortedIdx0,_sortedIdx1,_sortedIdx2); - // .. and this is a perfect hash for a face, provided - // there are fewer than 2097152 (2**21) points. But we - // keep the sorted indices around to resolve collisions - // - _hash = _sortedIdx0 << 42 ^ _sortedIdx1 << 21 ^ _sortedIdx2; - } - - size_t GetHash() const { - return _hash; - } - - bool operator == (const _IndexTri& other) const { - // A face is the same as another if the - // three sorted indices are identical. - // In practice, this function will only - // get called for extremely large meshes. - return (_sortedIdx0 == other._sortedIdx0) && - (_sortedIdx1 == other._sortedIdx1) && - (_sortedIdx2 == other._sortedIdx2); - } - - const GfVec3i& GetUnsortedIndices() const { - return _unsortedIndices; - } - - private: - - static void _SortFVIndices(size_t& a, size_t& b, size_t& c) { - if (a > b) { - std::swap(a, b); - } - - if (a > c) { - std::swap(a, c); - } - - if (b > c) { - std::swap(b, c); - } - } - - // Original unsorted indices - const GfVec3i _unsortedIndices; - - // Sorted Indices - size_t _sortedIdx0; - size_t _sortedIdx1; - size_t _sortedIdx2; - - // Precomputed hash - size_t _hash; - }; - - struct _IndexTriHash { - size_t operator()(const _IndexTri& tri) const - { - return tri.GetHash(); - } - }; - - struct _IndexTriEquals { - bool operator()(const _IndexTri& a, const _IndexTri& b) const - { - return a == b; - } - }; - - class FaceVertexIndicesCompare { - public: - // Comparator function - inline bool operator()(const GfVec3i& f1, const GfVec3i f2) - { - if (f1[0] == f2[0]) - { - if (f1[1] == f2[1]) - { - return f1[2] < f2[2]; - } - - return f1[1] < f2[1]; - } - - return f1[0] < f2[0]; - } - }; }; PXR_NAMESPACE_CLOSE_SCOPE