Skip to content

Commit

Permalink
UsdGeomTetMesh faces computation: changing the map so that the key do…
Browse files Browse the repository at this point in the history
…es not carry information that should be part of the value.

(Internal change: 2314459)
  • Loading branch information
unhyperbolic authored and pixar-oss committed Feb 5, 2024
1 parent 3ebc3ac commit ecb5c55
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 140 deletions.
154 changes: 114 additions & 40 deletions pxr/usd/usdGeom/tetMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<size_t>(v[0]) << 42 ^
static_cast<size_t>(v[1]) << 21 ^
static_cast<size_t>(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<GfVec3i, std::pair<size_t, GfVec3i>, _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<size_t, GfVec3i> &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;
}

Expand Down
100 changes: 0 additions & 100 deletions pxr/usd/usdGeom/tetMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit ecb5c55

Please sign in to comment.