testenv/testUsdGeomPurposeVisibility.py
testenv/testUsdGeomSchemata.py
testenv/testUsdGeomSubset.py
+ testenv/testUsdGeomTetMesh.py
testenv/testUsdGeomXformable.py
testenv/testUsdGeomXformCommonAPI.py
)
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
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"
--- /dev/null
+#!/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()
--- /dev/null
+#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)],
+ }
+}
+
// 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;
}
/// 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,
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