Skip to content

Commit

Permalink
Merge pull request #1432 from johnhaddon/facevaryingNormalSkinning
Browse files Browse the repository at this point in the history
USD PrimitiveAlgo : Fix loading of facevarying skinned normals
  • Loading branch information
johnhaddon authored Sep 16, 2024
2 parents b6350fb + fd80130 commit d9525b8
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 16 deletions.
4 changes: 3 additions & 1 deletion Changes
Original file line number Diff line number Diff line change
Expand Up @@ -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)
========
Expand Down
97 changes: 82 additions & 15 deletions contrib/IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ 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"
#include "pxr/usd/usdSkel/cache.h"
#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;
Expand Down Expand Up @@ -317,6 +319,53 @@ void applyBlendShapes( const pxr::UsdGeomPointBased &pointBased, pxr::UsdTimeCod
);
}

bool computeFaceVaryingSkinnedNormals( pxr::UsdSkelSkinningQuery &skinningQuery, const pxr::VtArray<pxr::GfMatrix4d> &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<pxr::GfMatrix4d> orderedXforms = xforms;
if( auto jointMapper = skinningQuery.GetJointMapper() )
{
if( !jointMapper->RemapTransforms( xforms, &orderedXforms ) )
{
return false;
}
}

Canceller::check( canceller );
pxr::VtArray<pxr::GfMatrix3d> 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 );
Expand Down Expand Up @@ -358,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 ) )
{
Expand Down Expand Up @@ -398,19 +443,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<V3fVectorData>( 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<V3fVectorData>( DataAlgo::fromUSD( normals ) );
n->setInterpretation( GeometricData::Normal );
addPrimitiveVariableIfValid(
primitive, "N", IECoreScene::PrimitiveVariable( PrimitiveAlgo::fromUSD( normalsInterpolation ), n ),
pointBased.GetNormalsAttr()
);
}

return true;
Expand Down
15 changes: 15 additions & 0 deletions contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) :

Expand Down
Original file line number Diff line number Diff line change
@@ -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 = </main/joint1>
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 = </main/joint1/anim>

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)]
}
}
}

0 comments on commit d9525b8

Please sign in to comment.