Skip to content

Commit 3f89ccc

Browse files
Merge pull request #1471 from johnhaddon/usdSkelFix
USDScene : Fix loading of instanced skinning with unique animation
2 parents 2e9f8ef + b717e5d commit 3f89ccc

File tree

4 files changed

+189
-0
lines changed

4 files changed

+189
-0
lines changed

Changes

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ Improvements
66

77
- USDScene : Added loading of ArnoldAlembic, ArnoldUsd and ArnoldProceduralCustom prims as Cortex ExternalProcedural objects.
88

9+
Fixes
10+
-----
11+
12+
- USDScene : Fixed loading of instanced UsdSkel geometry with unique animation applied.
13+
914
10.5.14.1 (relative to 10.5.14.0)
1015
=========
1116

contrib/IECoreUSD/src/IECoreUSD/USDScene.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ IECORE_PUSH_DEFAULT_VISIBILITY
7171
#include "pxr/usd/usdShade/material.h"
7272
#include "pxr/usd/usdShade/materialBindingAPI.h"
7373
#include "pxr/usd/usdShade/connectableAPI.h"
74+
#include "pxr/usd/usdSkel/bindingAPI.h"
7475
#include "pxr/usd/usdUtils/stageCache.h"
7576
#ifdef IECOREUSD_WITH_OPENVDB
7677
#include "pxr/usd/usdVol/fieldBase.h"
@@ -1764,6 +1765,17 @@ void USDScene::objectHash( double time, IECore::MurmurHash &h ) const
17641765
{
17651766
h.append( time );
17661767
}
1768+
// Account for the skinning applied by PrimitiveAlgo. Ideally this
1769+
// responsibility would be taken on by PrimitiveAlgo itself, but that
1770+
// would require modifying the ObjectAlgo API, which we don't want to
1771+
// do right now.
1772+
if( auto skelBindingAPI = pxr::UsdSkelBindingAPI( m_location->prim ) )
1773+
{
1774+
if( auto animationSource = skelBindingAPI.GetInheritedAnimationSource() )
1775+
{
1776+
appendPrimOrMasterPath( animationSource, h );
1777+
}
1778+
}
17671779
}
17681780
}
17691781
void USDScene::childNamesHash( double time, IECore::MurmurHash &h ) const

contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2588,6 +2588,49 @@ def testSkinnedFaceVaryingNormals( self ) :
25882588
for referenceNormal, normal in zip( referenceNormals.data, cubeMesh["N"].data ) :
25892589
self.assertTrue( normal.equalWithAbsError( referenceNormal, 0.000001 ) )
25902590

2591+
def testInstancedSkinning( self ) :
2592+
2593+
# Skinned meshes can be instanced, but with each instance inheriting different
2594+
# skeleton animation. Make sure we account for that.
2595+
2596+
root = IECoreScene.SceneInterface.create( os.path.dirname( __file__ ) + "/data/instancedSkinning.usda", IECore.IndexedIO.OpenMode.Read )
2597+
2598+
# Check that the skinned meshes come out with the expected skinning.
2599+
2600+
cube1 = root.scene( [ "Instance1", "SkeletonRoot", "SkinnedCube" ] )
2601+
self.assertEqual( cube1.readObject( 0 ).bound(), imath.Box3f( imath.V3f( -0.5, -0.5, 0.5 ), imath.V3f( 0.5, 0.5, 1.5 ) ) )
2602+
2603+
cube2 = root.scene( [ "Group", "Instance2", "SkeletonRoot", "SkinnedCube" ] )
2604+
self.assertEqual( cube2.readObject( 0 ).bound(), imath.Box3f( imath.V3f( -0.5, -0.5, -1.5 ), imath.V3f( 0.5, 0.5, -0.5 ) ) )
2605+
2606+
cube3 = root.scene( [ "Instance3", "SkeletonRoot", "SkinnedCube" ] )
2607+
self.assertEqual( cube2.readObject( 0 ).bound(), imath.Box3f( imath.V3f( -0.5, -0.5, -1.5 ), imath.V3f( 0.5, 0.5, -0.5 ) ) )
2608+
2609+
cube4 = root.scene( [ "Instance4", "SkeletonRoot", "SkinnedCube" ] )
2610+
self.assertEqual( cube2.readObject( 0 ).bound(), imath.Box3f( imath.V3f( -0.5, -0.5, -1.5 ), imath.V3f( 0.5, 0.5, -0.5 ) ) )
2611+
2612+
# And check that their object hashes match the results above.
2613+
2614+
ObjectHash = IECoreScene.SceneInterface.HashType.ObjectHash
2615+
self.assertNotEqual( cube1.hash( ObjectHash, 0 ), cube2.hash( ObjectHash, 0 ) ) # Different animation
2616+
self.assertEqual( cube2.hash( ObjectHash, 0 ), cube3.hash( ObjectHash, 0 ) ) # Same animation
2617+
self.assertEqual( cube2.hash( ObjectHash, 0 ), cube4.hash( ObjectHash, 0 ) ) # Same animation
2618+
2619+
# All the unskinned meshes should be the same.
2620+
2621+
unskinnedHashes = set()
2622+
for path in [
2623+
[ "Instance1", "SkeletonRoot", "UnskinnedCube" ],
2624+
[ "Group", "Instance2", "SkeletonRoot", "UnskinnedCube" ],
2625+
[ "Instance3", "SkeletonRoot", "UnskinnedCube" ],
2626+
[ "Instance4", "SkeletonRoot", "UnskinnedCube" ],
2627+
] :
2628+
cube = root.scene( path )
2629+
self.assertEqual( cube.readObject( 0 ).bound(), imath.Box3f( imath.V3f( -0.5, -0.5, -0.5 ), imath.V3f( 0.5, 0.5, 0.5 ) ) )
2630+
unskinnedHashes.add( cube.hash( ObjectHash, 0 ) )
2631+
2632+
self.assertEqual( len( unskinnedHashes ), 1 )
2633+
25912634
@unittest.skipIf( ( IECore.TestUtil.inMacCI() or IECore.TestUtil.inWindowsCI() ), "Mac and Windows CI are too slow for reliable timing" )
25922635
def testCancel ( self ) :
25932636

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#usda 1.0
2+
3+
# A prototype containing a skeleton and a couple of cubes, one of them skinned.
4+
5+
def Scope "Prototypes"
6+
{
7+
8+
uniform token visibility = "invisible"
9+
10+
def SkelRoot "SkeletonRoot" (
11+
prepend apiSchemas = ["SkelBindingAPI"]
12+
)
13+
{
14+
def Skeleton "Skeleton" (
15+
prepend apiSchemas = ["SkelBindingAPI"]
16+
)
17+
{
18+
uniform matrix4d[] bindTransforms = [( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )]
19+
uniform token[] joints = ["Joint1"]
20+
uniform matrix4d[] restTransforms = [( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )]
21+
}
22+
23+
def Mesh "SkinnedCube" (
24+
prepend apiSchemas = ["SkelBindingAPI"]
25+
)
26+
{
27+
int[] faceVertexCounts = [4, 4, 4, 4, 4, 4]
28+
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]
29+
uniform token subdivisionScheme = "none"
30+
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)]
31+
matrix4d primvars:skel:geomBindTransform = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )
32+
int[] primvars:skel:jointIndices = [0, 0, 0, 0, 0, 0, 0, 0] (
33+
elementSize = 1
34+
interpolation = "vertex"
35+
)
36+
float[] primvars:skel:jointWeights = [1, 1, 1, 1, 1, 1, 1, 1] (
37+
elementSize = 1
38+
interpolation = "vertex"
39+
)
40+
rel skel:skeleton = </Prototypes/SkeletonRoot/Skeleton>
41+
}
42+
43+
# Just regular geometry. Even though it's inside a SkelRoot, it
44+
# shouldn't be affected by SkelAnimation at all.
45+
def Mesh "UnskinnedCube"
46+
{
47+
int[] faceVertexCounts = [4, 4, 4, 4, 4, 4]
48+
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]
49+
uniform token subdivisionScheme = "none"
50+
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)]
51+
}
52+
}
53+
54+
}
55+
56+
# Instance of the prototype, with an animation inherited onto it.
57+
58+
def Xform "Instance1" (
59+
prepend apiSchemas = ["SkelBindingAPI"]
60+
)
61+
{
62+
append rel skel:animationSource = </Instance1/InlineAnim>
63+
64+
def SkelAnimation "InlineAnim"
65+
{
66+
uniform token[] joints = ["Joint1"]
67+
quatf[] rotations = [(1, 0, 0, 0)]
68+
half3[] scales = [(1, 1, 1)]
69+
float3[] translations = [(0, 0, 1)]
70+
}
71+
72+
over "SkeletonRoot" (
73+
instanceable = true
74+
prepend references = </Prototypes/SkeletonRoot>
75+
)
76+
{
77+
}
78+
}
79+
80+
# Another instance of the prototype, with a different animation inherited onto it.
81+
82+
def SkelAnimation "SeparateAnim"
83+
{
84+
uniform token[] joints = ["Joint1"]
85+
quatf[] rotations = [(1, 0, 0, 0)]
86+
half3[] scales = [(1, 1, 1)]
87+
float3[] translations = [(0, 0, -1)]
88+
}
89+
90+
def Xform "Group" (
91+
prepend apiSchemas = ["SkelBindingAPI"]
92+
)
93+
{
94+
append rel skel:animationSource = </SeparateAnim>
95+
96+
def Xform "Instance2"
97+
{
98+
over "SkeletonRoot" (
99+
instanceable = true
100+
prepend references = </Prototypes/SkeletonRoot>
101+
)
102+
{
103+
}
104+
}
105+
}
106+
107+
# A third instance, this time sharing the animation with the second instance.
108+
109+
def Xform "Instance3" (
110+
prepend apiSchemas = ["SkelBindingAPI"]
111+
)
112+
{
113+
append rel skel:animationSource = </SeparateAnim>
114+
over "SkeletonRoot" (
115+
instanceable = true
116+
prepend references = </Prototypes/SkeletonRoot>
117+
)
118+
{
119+
}
120+
}
121+
122+
# And now an instanceable reference to the third instance.
123+
124+
def Xform "Instance4" (
125+
instanceable = true
126+
prepend references = </Instance3>
127+
)
128+
{
129+
}

0 commit comments

Comments
 (0)