From 6f9983b52cac6562d3241f248e1d9024e41625a5 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 11 Sep 2024 15:28:08 +0100 Subject: [PATCH 1/2] USD PrimitiveAlgo : Fix loading of facevarying skinned normals --- Changes | 4 +- .../IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp | 93 ++++++++++++++++--- .../IECoreUSD/test/IECoreUSD/USDSceneTest.py | 15 +++ .../data/skinnedFaceVaryingNormals.usda | 66 +++++++++++++ 4 files changed, 166 insertions(+), 12 deletions(-) create mode 100644 contrib/IECoreUSD/test/IECoreUSD/data/skinnedFaceVaryingNormals.usda diff --git a/Changes b/Changes index ca35f2490c..a061d1f11b 100644 --- a/Changes +++ b/Changes @@ -4,7 +4,9 @@ Fixes ----- -- USDScene : `lightLink` and `shadowLink` collections on UsdLuxLightAPI are no longer treated as sets. +- USDScene : + - Fixed loading of skinned facevarying normals. + - `lightLink` and `shadowLink` collections on UsdLuxLightAPI are no longer treated as sets. 10.5.9.2 (relative to 10.5.9.1) ======== diff --git a/contrib/IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp b/contrib/IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp index 9add88c8eb..c20de4f961 100644 --- a/contrib/IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp +++ b/contrib/IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp @@ -45,6 +45,7 @@ IECORE_PUSH_DEFAULT_VISIBILITY #include "pxr/base/gf/matrix3d.h" #include "pxr/base/gf/matrix4f.h" #include "pxr/base/gf/matrix4d.h" +#include "pxr/usd/usdGeom/mesh.h" #include "pxr/usd/usdSkel/animQuery.h" #include "pxr/usd/usdSkel/bindingAPI.h" #include "pxr/usd/usdSkel/blendShapeQuery.h" @@ -52,6 +53,7 @@ IECORE_PUSH_DEFAULT_VISIBILITY #include "pxr/usd/usdSkel/skeletonQuery.h" #include "pxr/usd/usdSkel/skinningQuery.h" #include "pxr/usd/usdSkel/root.h" +#include "pxr/usd/usdSkel/utils.h" IECORE_POP_DEFAULT_VISIBILITY using namespace std; @@ -317,6 +319,53 @@ void applyBlendShapes( const pxr::UsdGeomPointBased &pointBased, pxr::UsdTimeCod ); } +bool computeFaceVaryingSkinnedNormals( pxr::UsdSkelSkinningQuery &skinningQuery, const pxr::VtArray &xforms, pxr::VtVec3fArray *normals, pxr::UsdTimeCode time, const Canceller *canceller ) +{ + const pxr::UsdGeomMesh mesh( skinningQuery.GetPrim() ); + if( !mesh ) + { + return false; + } + + Canceller::check( canceller ); + pxr::VtIntArray faceVertexIndices; + mesh.GetFaceVertexIndicesAttr().Get( &faceVertexIndices, time ); + + Canceller::check( canceller ); + pxr::VtIntArray jointIndices; + pxr::VtFloatArray jointWeights; + if( !skinningQuery.ComputeJointInfluences( &jointIndices, &jointWeights, time ) ) + { + return false; + } + + Canceller::check( canceller ); + pxr::VtArray orderedXforms = xforms; + if( auto jointMapper = skinningQuery.GetJointMapper() ) + { + if( !jointMapper->RemapTransforms( xforms, &orderedXforms ) ) + { + return false; + } + } + + Canceller::check( canceller ); + pxr::VtArray invTransposeXforms( orderedXforms.size() ); + for( size_t i = 0; i < xforms.size(); ++i ) + { + invTransposeXforms[i] = orderedXforms[i].ExtractRotationMatrix().GetInverse().GetTranspose(); + } + + Canceller::check( canceller ); + return pxr::UsdSkelSkinFaceVaryingNormals( + skinningQuery.GetSkinningMethod(), + skinningQuery.GetGeomBindTransform( time ).ExtractRotationMatrix().GetInverse().GetTranspose(), + invTransposeXforms, jointIndices, jointWeights, + skinningQuery.GetNumInfluencesPerComponent(), + faceVertexIndices, *normals + ); +} + bool readPrimitiveVariables( const pxr::UsdSkelRoot &skelRoot, const pxr::UsdGeomPointBased &pointBased, pxr::UsdTimeCode time, IECoreScene::Primitive *primitive, const Canceller *canceller ) { Canceller::check( canceller ); @@ -398,19 +447,41 @@ bool readPrimitiveVariables( const pxr::UsdSkelRoot &skelRoot, const pxr::UsdGeo pointBased.GetPointsAttr() ); - // we'll consider normals optional and return true regardless of whether normals were skinned successfully + // Normals + + Canceller::check( canceller ); pxr::VtVec3fArray normals; - if( pointBased.GetNormalsAttr().Get( &normals, time ) && skinningQuery.ComputeSkinnedNormals( skinningXforms, &normals, time ) ) + if( !pointBased.GetNormalsAttr().Get( &normals, time ) ) { - Canceller::check( canceller ); - if( auto n = boost::static_pointer_cast( DataAlgo::fromUSD( normals ) ) ) - { - n->setInterpretation( GeometricData::Normal ); - addPrimitiveVariableIfValid( - primitive, "N", IECoreScene::PrimitiveVariable( PrimitiveAlgo::fromUSD( pointBased.GetNormalsInterpolation() ), n ), - pointBased.GetNormalsAttr() - ); - } + // Now that we've skinned "P", we'll always return true, regardless of + // whether or not we can skin "N". + return true; + } + + const TfToken normalsInterpolation = pointBased.GetNormalsInterpolation(); + + Canceller::check( canceller ); + bool normalsValid = false; + if( normalsInterpolation == UsdGeomTokens->faceVarying ) + { + // UsdGeomSkinningQuery doesn't support facevarying normals. But + // there are lower-level functions we can use manually, so do that. + normalsValid = computeFaceVaryingSkinnedNormals( skinningQuery, skinningXforms, &normals, time, canceller ); + } + else + { + // UsdGeomSkinningQuery will do it all for us. + normalsValid = skinningQuery.ComputeSkinnedNormals( skinningXforms, &normals, time ); + } + + if( normalsValid ) + { + auto n = boost::static_pointer_cast( DataAlgo::fromUSD( normals ) ); + n->setInterpretation( GeometricData::Normal ); + addPrimitiveVariableIfValid( + primitive, "N", IECoreScene::PrimitiveVariable( PrimitiveAlgo::fromUSD( normalsInterpolation ), n ), + pointBased.GetNormalsAttr() + ); } return true; diff --git a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py index 43ff78c9c2..432ed8eff3 100644 --- a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py +++ b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py @@ -2569,6 +2569,21 @@ def testSkelBlendShapes( self ) : self.assertAlmostEqual( arm_10["P"].data[i].y, expected_10[i].y, 5 ) self.assertAlmostEqual( arm_10["P"].data[i].z, expected_10[i].z, 5 ) + def testSkinnedFaceVaryingNormals( self ) : + + root = IECoreScene.SceneInterface.create( os.path.dirname( __file__ ) + "/data/skinnedFaceVaryingNormals.usda", IECore.IndexedIO.OpenMode.Read ) + cubeLocation = root.scene( [ "main", "pCube1" ] ) + for timeSample in range( 1, 25 ) : + + cubeMesh = cubeLocation.readObject( timeSample / 24.0 ) + self.assertIn( "N", cubeMesh ) + self.assertTrue( cubeMesh.isPrimitiveVariableValid( cubeMesh["N"] ) ) + self.assertEqual( cubeMesh["N"].interpolation, IECoreScene.PrimitiveVariable.Interpolation.FaceVarying ) + + referenceNormals = IECoreScene.MeshAlgo.calculateFaceVaryingNormals( cubeMesh, thresholdAngle = 5 ) + for referenceNormal, normal in zip( referenceNormals.data, cubeMesh["N"].data ) : + self.assertTrue( normal.equalWithAbsError( referenceNormal, 0.000001 ) ) + @unittest.skipIf( ( IECore.TestUtil.inMacCI() or IECore.TestUtil.inWindowsCI() ), "Mac and Windows CI are too slow for reliable timing" ) def testCancel ( self ) : diff --git a/contrib/IECoreUSD/test/IECoreUSD/data/skinnedFaceVaryingNormals.usda b/contrib/IECoreUSD/test/IECoreUSD/data/skinnedFaceVaryingNormals.usda new file mode 100644 index 0000000000..a308c63e6c --- /dev/null +++ b/contrib/IECoreUSD/test/IECoreUSD/data/skinnedFaceVaryingNormals.usda @@ -0,0 +1,66 @@ +#usda 1.0 +( + defaultPrim = "main" + metersPerUnit = 0.01 + upAxis = "Y" +) + +def SkelRoot "main" ( + kind = "component" +) +{ + float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)] + + def Mesh "pCube1" ( + prepend apiSchemas = ["SkelBindingAPI"] + ) + { + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)] + int[] faceVertexCounts = [4, 4, 4, 4, 4, 4] + int[] faceVertexIndices = [0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 1, 0, 1, 7, 5, 3, 6, 0, 2, 4] + normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, -1, 0), (0, -1, 0), (0, -1, 0), (0, -1, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0)] ( + interpolation = "faceVarying" + ) + point3f[] points = [(-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (-0.5, 0.5, 0.5), (0.5, 0.5, 0.5), (-0.5, 0.5, -0.5), (0.5, 0.5, -0.5), (-0.5, -0.5, -0.5), (0.5, -0.5, -0.5)] + matrix4d primvars:skel:geomBindTransform = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) ) + int[] primvars:skel:jointIndices = [0, 0, 0, 0, 0, 0, 0, 0] ( + elementSize = 1 + interpolation = "vertex" + ) + float[] primvars:skel:jointWeights = [1, 1, 1, 1, 1, 1, 1, 1] ( + elementSize = 1 + interpolation = "vertex" + ) + uniform token[] skel:joints = ["joint1"] + rel skel:skeleton = + uniform token subdivisionScheme = "none" + } + + def Skeleton "joint1" ( + prepend apiSchemas = ["SkelBindingAPI"] + customData = { + dictionary Maya = { + bool generated = 1 + } + } + ) + { + uniform matrix4d[] bindTransforms = [( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )] + uniform token[] joints = ["joint1"] + uniform matrix4d[] restTransforms = [( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )] + rel skel:animationSource = + + def SkelAnimation "anim" + { + uniform token[] joints = ["joint1"] + quatf[] rotations.timeSamples = { + 1: [(1, 0, 0, 0)], + 24: [(0.7071, 0.7071, 0, 0)], + } + half3[] scales = [(1, 1, 1)] + float3[] translations = [(0, 0, 0)] + } + } +} + From fd80130600a78750b94377d807e83cc6ad158aef Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 11 Sep 2024 15:28:41 +0100 Subject: [PATCH 2/2] USD PrimitiveAlgo : Remove comment The remapping discussed appears to be performed for us inside `UsdSkelSkinningQuery::ComputeSkinnedPoints()` and `UsdSkelSkinningQuery::ComputeSkinnedNormals()`, as well as in our new facevarying normals code. That would explain why an attempt to apply it manually would lead to things getting jumbled up. --- contrib/IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/contrib/IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp b/contrib/IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp index c20de4f961..b7d43c2227 100644 --- a/contrib/IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp +++ b/contrib/IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp @@ -407,10 +407,6 @@ bool readPrimitiveVariables( const pxr::UsdSkelRoot &skelRoot, const pxr::UsdGeo Canceller::check( canceller ); applyBlendShapes( pointBased, time, skelQuery, skinningQuery, points ); - // The UsdSkelBakeSkinning example code uses skinningQuery.GetJointMapper() to remap - // xforms based on a per-prim joint order. However, doing this seems to scramble data - // for UsdSkel crowds exported from Houdini. We don't have any example data that requires - // the joint remapping, so for now we're omiting it in favor of more seamless DCC support. Canceller::check( canceller ); if( !skinningQuery.ComputeSkinnedPoints( skinningXforms, &points, time ) ) {