|
|
@@ -14,7 +14,7 @@ namespace SharpGLTF.Schema2
|
|
|
// https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Scene/Model.js#L2526
|
|
|
|
|
|
// max shader joints
|
|
|
- // https://github.com/KhronosGroup/glTF/issues/283
|
|
|
+ // https://github.com/KhronosGroup/COLLADA2GLTF/issues/110
|
|
|
|
|
|
#region lifecycle
|
|
|
|
|
|
@@ -59,36 +59,6 @@ namespace SharpGLTF.Schema2
|
|
|
|
|
|
#region API
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Finds all the skins that are using the given <see cref="Node"/> as a joint.
|
|
|
- /// </summary>
|
|
|
- /// <param name="jointNode">A <see cref="Node"/> joint.</param>
|
|
|
- /// <returns>A collection of <see cref="Skin"/> instances.</returns>
|
|
|
- internal static IEnumerable<Skin> FindSkinsUsingJoint(Node jointNode)
|
|
|
- {
|
|
|
- var idx = jointNode.LogicalIndex;
|
|
|
-
|
|
|
- return jointNode
|
|
|
- .LogicalParent
|
|
|
- .LogicalSkins
|
|
|
- .Where(s => s._joints.Contains(idx));
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Finds all the skins that are using the given <see cref="Node"/> as a skeleton.
|
|
|
- /// </summary>
|
|
|
- /// <param name="skeletonNode">A <see cref="Node"/> skeleton.</param>
|
|
|
- /// <returns>A collection of <see cref="Skin"/> instances.</returns>
|
|
|
- internal static IEnumerable<Skin> FindSkinsUsingSkeleton(Node skeletonNode)
|
|
|
- {
|
|
|
- var idx = skeletonNode.LogicalIndex;
|
|
|
-
|
|
|
- return skeletonNode
|
|
|
- .LogicalParent
|
|
|
- .LogicalSkins
|
|
|
- .Where(s => s._skeleton == idx);
|
|
|
- }
|
|
|
-
|
|
|
public Accessor GetInverseBindMatricesAccessor()
|
|
|
{
|
|
|
if (!this._inverseBindMatrices.HasValue) return null;
|
|
|
@@ -109,29 +79,66 @@ namespace SharpGLTF.Schema2
|
|
|
return new KeyValuePair<Node, Matrix4x4>(node, matrix);
|
|
|
}
|
|
|
|
|
|
- internal override void Validate(Validation.ValidationContext result)
|
|
|
+ public void BindJoints(params Node[] joints)
|
|
|
{
|
|
|
- base.Validate(result);
|
|
|
+ var rootJoint = _FindCommonAncestor(joints);
|
|
|
|
|
|
- // note: this check will fail if the buffers are not set
|
|
|
+ BindJoints(rootJoint.WorldMatrix, joints);
|
|
|
+ }
|
|
|
|
|
|
- for (int i = 0; i < _joints.Count; ++i)
|
|
|
- {
|
|
|
- var j = GetJoint(i);
|
|
|
+ public void BindJoints(Matrix4x4 meshBingTransform, params Node[] joints)
|
|
|
+ {
|
|
|
+ var meshBingInverse = meshBingTransform.Inverse();
|
|
|
+
|
|
|
+ var pairs = new KeyValuePair<Node, Matrix4x4>[joints.Length];
|
|
|
|
|
|
- var invXform = j.Value;
|
|
|
+ for (int i = 0; i < pairs.Length; ++i)
|
|
|
+ {
|
|
|
+ var xform = (joints[i].WorldMatrix * meshBingInverse).Inverse();
|
|
|
|
|
|
- if (invXform.M44 != 1) result.AddError(this, $"Joint {i} has invalid inverse matrix");
|
|
|
+ pairs[i] = new KeyValuePair<Node, Matrix4x4>(joints[i], xform);
|
|
|
}
|
|
|
+
|
|
|
+ BindJoints(pairs);
|
|
|
}
|
|
|
|
|
|
+ public void BindJoints(KeyValuePair<Node, Matrix4x4>[] joints)
|
|
|
+ {
|
|
|
+ _FindCommonAncestor(joints.Select(item => item.Key));
|
|
|
+
|
|
|
+ foreach (var j in joints) { Guard.IsTrue(j.Value._IsReal(), nameof(joints)); }
|
|
|
+
|
|
|
+ // inverse bind matrices accessor
|
|
|
+
|
|
|
+ var data = new Byte[joints.Length * 16 * 4];
|
|
|
+
|
|
|
+ var matrices = new Memory.Matrix4x4Array(data.Slice(0), 0, EncodingType.FLOAT, false);
|
|
|
+
|
|
|
+ matrices.Fill(joints.Select(item => item.Value));
|
|
|
+
|
|
|
+ var accessor = LogicalParent.CreateAccessor("Bind Matrices");
|
|
|
+
|
|
|
+ accessor.SetData( LogicalParent.UseBufferView(data), 0, joints.Length, DimensionType.MAT4, EncodingType.FLOAT, false);
|
|
|
+
|
|
|
+ this._inverseBindMatrices = accessor.LogicalIndex;
|
|
|
+
|
|
|
+ // joints
|
|
|
+
|
|
|
+ _joints.Clear();
|
|
|
+ _joints.AddRange(joints.Select(item => item.Key.LogicalIndex));
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region helpers
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Returns true if this <see cref="Skin"/> matches the input values.
|
|
|
/// </summary>
|
|
|
/// <param name="skeleton">A <see cref="Node"/> instance that represents the skeleton root.</param>
|
|
|
/// <param name="joints">A key value pair collection of <see cref="Node"/> joints and their binding matrices.</param>
|
|
|
/// <returns>True if the input values match this <see cref="Skin"/>.</returns>
|
|
|
- public bool IsMatch(Node skeleton, KeyValuePair<Node, Matrix4x4>[] joints)
|
|
|
+ internal bool IsMatch(Node skeleton, KeyValuePair<Node, Matrix4x4>[] joints)
|
|
|
{
|
|
|
if (!ReferenceEquals(skeleton, this.Skeleton)) return false;
|
|
|
|
|
|
@@ -149,56 +156,97 @@ namespace SharpGLTF.Schema2
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- public void BindJoints(params Node[] joints)
|
|
|
+ /// <summary>
|
|
|
+ /// Finds all the skins that are using the given <see cref="Node"/> as a joint.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="jointNode">A <see cref="Node"/> joint.</param>
|
|
|
+ /// <returns>A collection of <see cref="Skin"/> instances.</returns>
|
|
|
+ internal static IEnumerable<Skin> FindSkinsUsingJoint(Node jointNode)
|
|
|
{
|
|
|
- foreach (var j in joints) Guard.MustShareLogicalParent(this, j, nameof(joints));
|
|
|
+ var idx = jointNode.LogicalIndex;
|
|
|
|
|
|
- var rootJoint = joints.Select(item => item.VisualRoot).Distinct().ToList();
|
|
|
+ return jointNode
|
|
|
+ .LogicalParent
|
|
|
+ .LogicalSkins
|
|
|
+ .Where(s => s._joints.Contains(idx));
|
|
|
+ }
|
|
|
|
|
|
- Guard.IsTrue(rootJoint.Count == 1, nameof(joints), "All joints must have only one root parent");
|
|
|
+ /// <summary>
|
|
|
+ /// Finds all the skins that are using the given <see cref="Node"/> as a skeleton.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="skeletonNode">A <see cref="Node"/> skeleton.</param>
|
|
|
+ /// <returns>A collection of <see cref="Skin"/> instances.</returns>
|
|
|
+ internal static IEnumerable<Skin> FindSkinsUsingSkeleton(Node skeletonNode)
|
|
|
+ {
|
|
|
+ var idx = skeletonNode.LogicalIndex;
|
|
|
|
|
|
- var skeleton = rootJoint[0];
|
|
|
+ return skeletonNode
|
|
|
+ .LogicalParent
|
|
|
+ .LogicalSkins
|
|
|
+ .Where(s => s._skeleton == idx);
|
|
|
+ }
|
|
|
|
|
|
- this.Skeleton = skeleton;
|
|
|
+ /// <summary>
|
|
|
+ /// Validates the node tree, ensuring that all nodes share a common ancestor node, and returns it.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="nodes">A collection of <see cref="Node"/> joints arranged in a tree.</param>
|
|
|
+ /// <returns>The <see cref="Node"/> root of the tree.</returns>
|
|
|
+ private Node _FindCommonAncestor(IEnumerable<Node> nodes)
|
|
|
+ {
|
|
|
+ foreach (var j in nodes) Guard.MustShareLogicalParent(this, j, nameof(nodes));
|
|
|
|
|
|
- Matrix4x4.Invert(skeleton.WorldMatrix, out Matrix4x4 skeletonBindXform);
|
|
|
+ var workingNodes = nodes.ToList();
|
|
|
|
|
|
- var pairs = new KeyValuePair<Node, Matrix4x4>[joints.Length];
|
|
|
+ var rootJoint = workingNodes.First();
|
|
|
|
|
|
- for (int i = 0; i < pairs.Length; ++i)
|
|
|
+ while (true)
|
|
|
{
|
|
|
- var xform = joints[i].WorldMatrix * skeletonBindXform;
|
|
|
+ if (workingNodes.All(j => rootJoint == j || rootJoint._ContainsVisualNode(j, true))) return rootJoint;
|
|
|
|
|
|
- Matrix4x4.Invert(xform, out Matrix4x4 ixform);
|
|
|
+ if (rootJoint.VisualParent == null) break;
|
|
|
|
|
|
- pairs[i] = new KeyValuePair<Node, Matrix4x4>(joints[i], ixform);
|
|
|
+ rootJoint = rootJoint.VisualParent;
|
|
|
}
|
|
|
|
|
|
- BindJoints(pairs);
|
|
|
+ Guard.IsTrue(false, nameof(nodes), "Common ancestor not found");
|
|
|
+ return null;
|
|
|
}
|
|
|
|
|
|
- public void BindJoints(KeyValuePair<Node, Matrix4x4>[] joints)
|
|
|
- {
|
|
|
- foreach (var j in joints) Guard.MustShareLogicalParent(this, j.Key, nameof(joints));
|
|
|
-
|
|
|
- // inverse bind matrices accessor
|
|
|
-
|
|
|
- var data = new Byte[joints.Length * 16 * 4];
|
|
|
+ #endregion
|
|
|
|
|
|
- var matrices = new Memory.Matrix4x4Array(data.Slice(0), 0, EncodingType.FLOAT, false);
|
|
|
+ #region validation
|
|
|
|
|
|
- matrices.Fill(joints.Select(item => item.Value));
|
|
|
+ internal override void Validate(Validation.ValidationContext result)
|
|
|
+ {
|
|
|
+ base.Validate(result);
|
|
|
|
|
|
- var accessor = LogicalParent.CreateAccessor("Bind Matrices");
|
|
|
+ var ibxAccessor = GetInverseBindMatricesAccessor();
|
|
|
|
|
|
- accessor.SetData( LogicalParent.UseBufferView(data), 0, joints.Length, DimensionType.MAT4, EncodingType.FLOAT, false);
|
|
|
+ var logicalNodeCount = this.LogicalParent.LogicalNodes.Count;
|
|
|
|
|
|
- this._inverseBindMatrices = accessor.LogicalIndex;
|
|
|
+ if (_skeleton.HasValue)
|
|
|
+ {
|
|
|
+ if (_skeleton.Value < 0 || _skeleton.Value >= logicalNodeCount) result.AddError(this, $"Skeleton Node index reference is out of bounds.");
|
|
|
+ }
|
|
|
|
|
|
- // joints
|
|
|
+ if (_joints == null || _joints.Count < _jointsMinItems)
|
|
|
+ {
|
|
|
+ result.AddError(this, $"Expected at least {_jointsMinItems} Joints");
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- _joints.Clear();
|
|
|
- _joints.AddRange(joints.Select(item => item.Key.LogicalIndex));
|
|
|
+ for (int i = 0; i < _joints.Count; ++i)
|
|
|
+ {
|
|
|
+ var jidx = _joints[i];
|
|
|
+ if (jidx < 0 || jidx >= logicalNodeCount) result.AddError(this, $"Joint {i} Node index reference is out of bounds.");
|
|
|
+
|
|
|
+ if (ibxAccessor != null)
|
|
|
+ {
|
|
|
+ var ibxform = ibxAccessor.AsMatrix4x4Array()[i];
|
|
|
+ try { ibxform.Inverse(); }
|
|
|
+ catch { result.AddError(this, $"Joint {i} has invalid bind matrix"); }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
#endregion
|