Add unit test for UsdGeomTetMesh that exercises surface extraction.
authoreberled <eberled@users.noreply.github.com>
Tue, 19 Dec 2023 23:50:29 +0000 (15:50 -0800)
committerpixar-oss <pixar-oss@users.noreply.github.com>
Tue, 19 Dec 2023 23:50:29 +0000 (15:50 -0800)
Changes to custom code in tetMesh.h/tetMesh.cpp
    - to get deterministic order of the surfafce face indices across compilers/OS, the resulting surface faces must be sorted.

(Internal change: 2309441)

pxr/usd/usdGeom/CMakeLists.txt
pxr/usd/usdGeom/testenv/testUsdGeomTetMesh.py [new file with mode: 0644]
pxr/usd/usdGeom/testenv/testUsdGeomTetMesh/baseline/tetMesh.usda [new file with mode: 0644]
pxr/usd/usdGeom/tetMesh.cpp
pxr/usd/usdGeom/tetMesh.h

index 41abe415ef8ec70c8c1edec68a7d15be899c6dd7..08eb2e068454b856457d747bf3d2151601c0d72c 100644 (file)
@@ -148,6 +148,7 @@ pxr_test_scripts(
     testenv/testUsdGeomPurposeVisibility.py
     testenv/testUsdGeomSchemata.py
     testenv/testUsdGeomSubset.py
+    testenv/testUsdGeomTetMesh.py
     testenv/testUsdGeomXformable.py
     testenv/testUsdGeomXformCommonAPI.py
 )
@@ -236,11 +237,18 @@ pxr_install_test_dir(
 
 pxr_install_test_dir(
     SRC testenv/testUsdGeomImageable
-    DEST testUsdGeomImageable)
+    DEST testUsdGeomImageable
+)
 
 pxr_install_test_dir(
     SRC testenv/testUsdGeomMesh
-    DEST testUsdGeomMesh)
+    DEST testUsdGeomMesh
+)
+
+pxr_install_test_dir(
+    SRC testenv/testUsdGeomTetMesh
+    DEST testUsdGeomTetMesh
+)
 
 pxr_register_test(testUsdGeomBasisCurves
     PYTHON
@@ -292,13 +300,18 @@ pxr_register_test(testUsdGeomMesh
     EXPECTED_RETURN_CODE 0
 )
 
+pxr_register_test(testUsdGeomTetMesh
+    PYTHON
+    COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdGeomTetMesh"
+    DIFF_COMPARE tetMesh.usda
+)
+
 pxr_register_test(testUsdGeomMetrics
     PYTHON
     COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdGeomMetrics"
     EXPECTED_RETURN_CODE 0
 )
 
-
 pxr_register_test(testUsdGeomSubset
     PYTHON
     COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdGeomSubset"
diff --git a/pxr/usd/usdGeom/testenv/testUsdGeomTetMesh.py b/pxr/usd/usdGeom/testenv/testUsdGeomTetMesh.py
new file mode 100644 (file)
index 0000000..4ddcbf3
--- /dev/null
@@ -0,0 +1,104 @@
+#!/pxrpythonsubst
+#
+# Copyright 2023 Pixar
+#
+# Licensed under the Apache License, Version 2.0 (the "Apache License")
+# with the following modification; you may not use this file except in
+# compliance with the Apache License and the following modification to it:
+# Section 6. Trademarks. is deleted and replaced with:
+#
+# 6. Trademarks. This License does not grant permission to use the trade
+#    names, trademarks, service marks, or product names of the Licensor
+#    and its affiliates, except as required to comply with Section 4(c) of
+#    the License and to reproduce the content of the NOTICE file.
+#
+# You may obtain a copy of the Apache License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the Apache License with the above modification is
+# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the Apache License for the specific
+# language governing permissions and limitations under the Apache License.
+
+import sys, unittest
+from pxr import Usd, UsdGeom, Vt, Gf
+
+class TestUsdGeomTetMesh(unittest.TestCase):
+    # Tests time varying topology and surface computation
+    def test_ComputeSurfaceExtractionFromUsdGeomTetMesh(self):
+        stage = Usd.Stage.CreateInMemory()
+        myTetMesh = UsdGeom.TetMesh.Define(stage,"/tetMesh")
+        pointsAttr = myTetMesh.GetPointsAttr()
+
+        pointsTime0 = Vt.Vec3fArray(5, (Gf.Vec3f(0.0, 0.0, 0.0),
+                                        Gf.Vec3f(2.0, 0.0, 0.0),
+                                        Gf.Vec3f(0.0, 2.0, 0.0),
+                                        Gf.Vec3f(0.0, 0.0, 2.0),
+                                        Gf.Vec3f(0.0, 0.0, -2.0)))
+
+        pointsAttr.Set(pointsTime0, 0.0)
+
+        pointsTime10 = Vt.Vec3fArray(8, (Gf.Vec3f(0.0, 0.0, 3.0),
+                                        Gf.Vec3f(2.0, 0.0, 3.0),
+                                        Gf.Vec3f(0.0, 2.0, 3.0),
+                                        Gf.Vec3f(0.0, 0.0, 5.0),
+                                        Gf.Vec3f(0.0, 0.0, -3.0),
+                                        Gf.Vec3f(2.0, 0.0, -3.0),
+                                        Gf.Vec3f(0.0, 2.0, -3.0),
+                                        Gf.Vec3f(0.0, 0.0, -5.0)))
+
+        pointsAttr.Set(pointsTime10, 10.0)
+
+        tetVertexIndicesAttr = myTetMesh.GetTetVertexIndicesAttr();
+        tetIndicesTime0 = Vt.Vec4iArray(2, (Gf.Vec4i(0,1,2,3),
+                                            Gf.Vec4i(0,2,1,4)))
+
+        tetVertexIndicesAttr.Set(tetIndicesTime0, 0.0)
+
+        tetIndicesTime10 = Vt.Vec4iArray(2, (Gf.Vec4i(0,1,2,3),
+                                             Gf.Vec4i(4,6,5,7)))
+
+        tetVertexIndicesAttr.Set(tetIndicesTime10, 10.0)
+
+        surfaceFacesTime0 = UsdGeom.TetMesh.ComputeSurfaceFaces(myTetMesh, 0.0)
+
+        # When the tets are joined we have 6 faces
+        self.assertEqual(len(surfaceFacesTime0), 6)
+
+        surfaceFacesTime10 = UsdGeom.TetMesh.ComputeSurfaceFaces(myTetMesh, 10.0)
+        # When they separate we have 8 faces
+        self.assertEqual(len(surfaceFacesTime10), 8)
+
+        triMesh = UsdGeom.Mesh.Define(stage,"/triMesh")
+        triMeshPointsAttr = triMesh.GetPointsAttr()
+        triMeshPointsAttr.Set(pointsTime0, 0.0)
+        triMeshPointsAttr.Set(pointsTime10, 10.0)
+        triMeshFaceVertexCountsAttr = triMesh.GetFaceVertexCountsAttr()
+
+        faceVertexCountsTime0 = Vt.IntArray(6, (3,3,3,3,3,3))
+        triMeshFaceVertexCountsAttr.Set(faceVertexCountsTime0, 0.0)
+        faceVertexCountsTime10 = Vt.IntArray(8, (3,3,3,3,3,3,3,3))
+        triMeshFaceVertexCountsAttr.Set(faceVertexCountsTime10, 10.0)
+
+        # Need to convert surfaceFaceIndices from VtVec3iArray to VtIntArray
+        faceVertexIndicesTime0 = Vt.IntArray(18)
+        for i in range(0,6):
+            for j in range(0,3):
+                faceVertexIndicesTime0[i * 3 + j] = surfaceFacesTime0[i][j]
+
+        faceVertexIndicesTime10 = Vt.IntArray(24)
+        for i in range(0,8):
+            for j in range(0,3):
+                faceVertexIndicesTime10[i * 3 + j] = surfaceFacesTime10[i][j]
+
+        triMeshFaceVertexIndicesAttr = triMesh.GetFaceVertexIndicesAttr()
+        triMeshFaceVertexIndicesAttr.Set(faceVertexIndicesTime0, 0.0)
+        triMeshFaceVertexIndicesAttr.Set(faceVertexIndicesTime10, 10.0)
+        stage.SetStartTimeCode(0.0)
+        stage.SetEndTimeCode(15.0)
+        stage.Export('tetMesh.usda')
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/pxr/usd/usdGeom/testenv/testUsdGeomTetMesh/baseline/tetMesh.usda b/pxr/usd/usdGeom/testenv/testUsdGeomTetMesh/baseline/tetMesh.usda
new file mode 100644 (file)
index 0000000..6cc0a1e
--- /dev/null
@@ -0,0 +1,36 @@
+#usda 1.0
+(
+    doc = """Generated from Composed Stage of root layer 
+"""
+    endTimeCode = 15
+    startTimeCode = 0
+)
+
+def TetMesh "tetMesh"
+{
+    point3f[] points.timeSamples = {
+        0: [(0, 0, 0), (2, 0, 0), (0, 2, 0), (0, 0, 2), (0, 0, -2)],
+        10: [(0, 0, 3), (2, 0, 3), (0, 2, 3), (0, 0, 5), (0, 0, -3), (2, 0, -3), (0, 2, -3), (0, 0, -5)],
+    }
+    int4[] tetVertexIndices.timeSamples = {
+        0: [(0, 1, 2, 3), (0, 2, 1, 4)],
+        10: [(0, 1, 2, 3), (4, 6, 5, 7)],
+    }
+}
+
+def Mesh "triMesh"
+{
+    int[] faceVertexCounts.timeSamples = {
+        0: [3, 3, 3, 3, 3, 3],
+        10: [3, 3, 3, 3, 3, 3, 3, 3],
+    }
+    int[] faceVertexIndices.timeSamples = {
+        0: [0, 1, 3, 0, 2, 4, 0, 3, 2, 0, 4, 1, 1, 2, 3, 2, 1, 4],
+        10: [0, 1, 3, 0, 2, 1, 0, 3, 2, 1, 2, 3, 4, 5, 6, 4, 6, 7, 4, 7, 5, 6, 5, 7],
+    }
+    point3f[] points.timeSamples = {
+        0: [(0, 0, 0), (2, 0, 0), (0, 2, 0), (0, 0, 2), (0, 0, -2)],
+        10: [(0, 0, 3), (2, 0, 3), (0, 2, 3), (0, 0, 5), (0, 0, -3), (2, 0, -3), (0, 2, -3), (0, 0, -5)],
+    }
+}
+
index 83e5a166e311ce107b4655fd271cbd8752cb5eb4..3c07c7b2c7b667c84c41ffc9a50473aaa52c19fb 100644 (file)
@@ -227,12 +227,17 @@ bool UsdGeomTetMesh::ComputeSurfaceFaces(const UsdGeomTetMesh& tetMesh,
     // per tetrahedron.  
     surfaceFaceIndices->reserve(tetVertexIndices.size());
 
-    TF_FOR_ALL(iter, triangleCounts) {
-        if (iter->second == 1) {
-            const _IndexTri& tri = iter->first;
+    for(auto&& [first, second] : triangleCounts) {
+        if (second == 1) {
+            const _IndexTri& tri = first;
             surfaceFaceIndices->push_back(tri.GetUnsortedIndices());
         }
     }
+    // Need to sort results for deterministic behavior across different 
+    // compiler/OS versions 
+    FaceVertexIndicesCompare comparator;
+    std::sort(surfaceFaceIndices->begin(), 
+              surfaceFaceIndices->end(), comparator);
 
     return true;
 }
index 9f20adda9dd568f11072f07f6bc1254f5f32f778..11079f835b37126dfafbb8dce57e6c78d6eb54ee 100644 (file)
@@ -232,8 +232,11 @@ public:
     /// ComputeSurfaceFaces determines the vertex indices of the surface faces 
     /// from tetVertexIndices. The surface faces are the set of faces that occur 
     /// only once when traversing the faces of all the tetrahedra. The algorithm 
-    /// is O(n) in the number of tetrahedra. Method returns false if 
+    /// is O(nlogn) in the number of tetrahedra. Method returns false if 
     /// surfaceFaceIndices argument is nullptr and returns true otherwise.
+    /// The algorithm can't be O(n) because we need to sort the resulting
+    /// surface faces for deterministic behavior across different compilers 
+    /// and OS. 
     USDGEOM_API    
     static bool ComputeSurfaceFaces(const UsdGeomTetMesh& tetMesh,
                                     VtVec3iArray* surfaceFaceIndices,
@@ -319,6 +322,25 @@ private:
             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