@@ -8,6 +8,8 @@ namespace SharpGLTF.Schema2
88 [ System . Diagnostics . DebuggerDisplay ( "Skin[{LogicalIndex}] {Name}" ) ]
99 public sealed partial class Skin
1010 {
11+ // https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#skins
12+
1113 // https://github.com/KhronosGroup/glTF/issues/461
1214 // https://github.com/KhronosGroup/glTF/issues/100
1315 // https://github.com/KhronosGroup/glTF/issues/403
@@ -33,32 +35,88 @@ internal Skin()
3335 public IEnumerable < Node > VisualParents => Node . FindNodesUsingSkin ( this ) ;
3436
3537 /// <summary>
36- /// Gets the number of joints
38+ /// Gets the number of joints.
3739 /// </summary>
3840 public int JointsCount => _joints . Count ;
3941
4042 /// <summary>
41- /// Gets or sets the Skeleton <see cref="Node"/>, which represents the root of a joints hierarchy.
43+ /// Gets the list of joints.
44+ /// </summary>
45+ /// <remarks>
46+ /// Each joint is correlated to its respective IBM in <see cref="InverseBindMatrices"/>.
47+ /// </remarks>
48+ public IReadOnlyList < Node > Joints => _joints . SelectList ( idx => this . LogicalParent . LogicalNodes [ idx ] ) ;
49+
50+ /// <summary>
51+ /// Gets the list of Inverse Bind Matrices.
52+ /// </summary>
53+ /// <remarks>
54+ /// Each IBM is correlated to its respective Joint in <see cref="Joints"/>.
55+ /// </remarks>
56+ public IReadOnlyList < Matrix4x4 > InverseBindMatrices
57+ {
58+ get
59+ {
60+ var matrices = GetInverseBindMatricesAccessor ( ) ;
61+ if ( matrices == null ) return Array . Empty < Matrix4x4 > ( ) ;
62+
63+ System . Diagnostics . Debug . Assert ( matrices . Count == _joints . Count , "IBM and Joints count mismatch" ) ;
64+
65+ return matrices . AsMatrix4x4ReadOnlyList ( ) ;
66+ }
67+ }
68+
69+ /// <summary>
70+ /// Gets or sets the Skeleton <see cref="Node"/>, which represents the root of a joints hierarchy AKA the Armature Root.
4271 /// </summary>
72+ /// <remarks>
73+ /// <para>
74+ /// As per <see href="https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#skins-overview">glTF specification</see>,
75+ /// this value is optional. So don't expect all glTF models to have this property set.
76+ /// </para>
77+ /// <para>
78+ /// Although the skeleton property is not needed for computing skinning transforms, it may be used to provide a specific “pivot point” for the skinned geometry.
79+ /// </para>
80+ /// </remarks>
4381 public Node Skeleton
4482 {
45- get => this . _skeleton . HasValue ? this . LogicalParent . LogicalNodes [ this . _skeleton . Value ] : null ;
83+ get
84+ {
85+ return this . _skeleton . HasValue
86+ ? this . LogicalParent . LogicalNodes [ this . _skeleton . Value ]
87+ : null ;
88+ }
89+
4690 set
4791 {
4892 if ( value != null ) Guard . MustShareLogicalParent ( this . LogicalParent , nameof ( this . LogicalParent ) , value , nameof ( value ) ) ;
49- this . _skeleton = value == null ? ( int ? ) null : value . LogicalIndex ;
93+ this . _skeleton = value == null
94+ ? ( int ? ) null
95+ : value . LogicalIndex ;
5096 }
5197 }
5298
5399 #endregion
54100
55101 #region API
56102
57- public Accessor GetInverseBindMatricesAccessor ( )
103+ public Accessor UseInverseBindMatricesAccessor ( )
58104 {
59- if ( ! this . _inverseBindMatrices . HasValue ) return null ;
105+ var accessor = GetInverseBindMatricesAccessor ( ) ;
106+ if ( accessor == null )
107+ {
108+ accessor = LogicalParent . CreateAccessor ( "Bind Matrices" ) ;
109+ this . _inverseBindMatrices = accessor . LogicalIndex ;
110+ }
111+
112+ return accessor ;
113+ }
60114
61- return this . LogicalParent . LogicalAccessors [ this . _inverseBindMatrices . Value ] ;
115+ public Accessor GetInverseBindMatricesAccessor ( )
116+ {
117+ return _inverseBindMatrices . HasValue
118+ ? this . LogicalParent . LogicalAccessors [ this . _inverseBindMatrices . Value ]
119+ : null ;
62120 }
63121
64122 public ( Node Joint , Matrix4x4 InverseBindMatrix ) GetJoint ( int idx )
@@ -69,7 +127,9 @@ public Accessor GetInverseBindMatricesAccessor()
69127
70128 var matrices = GetInverseBindMatricesAccessor ( ) ;
71129
72- var matrix = matrices == null ? Matrix4x4 . Identity : matrices . AsMatrix4x4Array ( ) [ idx ] ;
130+ var matrix = matrices == null
131+ ? Matrix4x4 . Identity
132+ : matrices . AsMatrix4x4Array ( ) [ idx ] ;
73133
74134 return ( node , matrix ) ;
75135 }
@@ -82,7 +142,7 @@ public void BindJoints(params Node[] joints)
82142 }
83143
84144 /// <summary>
85- /// Binds a bone armature of <see cref="Node"/> to the associated skinned mesh.
145+ /// Binds the armature <see cref="Node"/>s to the associated skinned mesh.
86146 /// </summary>
87147 /// <param name="meshBindTransform">The world transform matrix of the mesh at the time of binding.</param>
88148 /// <param name="joints">A collection of <see cref="Node"/> joints.</param>
@@ -114,47 +174,47 @@ public void BindJoints(Matrix4x4 meshBindTransform, params Node[] joints)
114174 /// A collection of <see cref="Node"/> joints,
115175 /// where each joint has an Inverse Bind Matrix.
116176 /// </param>
117- public void BindJoints ( ( Node Joint , Matrix4x4 InverseBindMatrix ) [ ] joints )
177+ public void BindJoints ( IReadOnlyList < ( Node Joint , Matrix4x4 InverseBindMatrix ) > joints )
118178 {
119179 Guard . NotNull ( joints , nameof ( joints ) ) ;
120180
121181 _FindCommonAncestor ( joints . Select ( item => item . Joint ) ) ;
182+
183+ // sanitize IBMs
122184
123- // Acording to gltf schema
124- // "The fourth row of each matrix MUST be set to [0.0, 0.0, 0.0, 1.0]."
125- // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skins-overview
126- for ( int i = 0 ; i < joints . Length ; ++ i )
185+ Matrix4x4 _SanitizedIBM ( Matrix4x4 ibm , int idx )
127186 {
128- var ibm = joints [ i ] . InverseBindMatrix ;
187+ Transforms . Matrix4x4Factory . GuardMatrix ( $ " { nameof ( joints ) } [ { idx } ]" , ibm , Transforms . Matrix4x4Factory . MatrixCheck . InverseBindMatrix , 0.01f ) ;
129188
130- Transforms . Matrix4x4Factory . GuardMatrix ( $ "{ nameof ( joints ) } [{ i } ]", ibm , Transforms . Matrix4x4Factory . MatrixCheck . InverseBindMatrix , 0.01f ) ;
189+ // Acording to gltf specs https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skins-overview
190+ // "The fourth row of each matrix MUST be set to [0.0, 0.0, 0.0, 1.0]."
131191
132- // fourth column (row in schema) is within tolerance
133- // so we can enforce exact values,
192+ // fourth column (row in schema) has passed the guard and it is within tolerance so we can enforce exact values,
134193 ibm . M14 = 0 ;
135194 ibm . M24 = 0 ;
136195 ibm . M34 = 0 ;
137196 ibm . M44 = 1 ;
138197
139- joints [ i ] = ( joints [ i ] . Joint , ibm ) ;
198+ return ibm ;
140199 }
141200
201+ var ibms = joints . Select ( ( item , idx ) => _SanitizedIBM ( item . InverseBindMatrix , idx ) ) ;
202+
142203 // inverse bind matrices accessor
143204
144- var data = new Byte [ joints . Length * 16 * 4 ] ;
205+ var data = new Byte [ joints . Count * 16 * 4 ] ;
145206 var matrices = new Memory . Matrix4x4Array ( data , 0 , EncodingType . FLOAT , false ) ;
146- matrices . Fill ( joints . Select ( item => item . InverseBindMatrix ) ) ;
207+ matrices . Fill ( ibms ) ;
147208
148- var accessor = LogicalParent . CreateAccessor ( "Bind Matrices" ) ;
149- accessor . SetData ( LogicalParent . UseBufferView ( data ) , 0 , joints . Length , DimensionType . MAT4 , EncodingType . FLOAT , false ) ;
209+ var ibmsView = LogicalParent . UseBufferView ( data ) ;
150210
151- this . _inverseBindMatrices = accessor . LogicalIndex ;
211+ UseInverseBindMatricesAccessor ( ) . SetData ( ibmsView , 0 , joints . Count , DimensionType . MAT4 , EncodingType . FLOAT , false ) ;
152212
153213 // joints
154214
155215 _joints . Clear ( ) ;
156216 _joints . AddRange ( joints . Select ( item => item . Joint . LogicalIndex ) ) ;
157- }
217+ }
158218
159219 #endregion
160220
0 commit comments