Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

USD PrimitiveAlgo : Fix loading of facevarying skinned normals #1432

Merged
merged 2 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)]
}
}
}

Loading