Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
fe3105350This file is pinned to the selected release. Download the original to open it in your design tool.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace SharpGLTF.Schema2
{
[System.Diagnostics.DebuggerDisplay("Skin[{LogicalIndex}] {Name}")]
public sealed partial class Skin
{
// https://github.com/KhronosGroup/glTF/issues/461
// https://github.com/KhronosGroup/glTF/issues/100
// https://github.com/KhronosGroup/glTF/issues/403
// https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Scene/Model.js#L2526
// max shader joints
// https://github.com/KhronosGroup/COLLADA2GLTF/issues/110
#region lifecycle
internal Skin()
{
_joints = new List<int>();
}
#endregion
#region properties
/// <summary>
/// Gets a collection of <see cref="Node"/> instances using this <see cref="Skin"/>.
/// </summary>
public IEnumerable<Node> VisualParents => Node.FindNodesUsingSkin(this);
/// <summary>
/// Gets the number of joints
/// </summary>
public int JointsCount => _joints.Count;
/// <summary>
/// Gets or sets the Skeleton <see cref="Node"/>, which represents the root of a joints hierarchy.
/// </summary>
public Node Skeleton
{
get => this._skeleton.HasValue ? this.LogicalParent.LogicalNodes[this._skeleton.Value] : null;
set
{
if (value != null) Guard.MustShareLogicalParent(this.LogicalParent, nameof(this.LogicalParent), value, nameof(value));
this._skeleton = value == null ? (int?)null : value.LogicalIndex;
}
}
#endregion
#region API
public Accessor GetInverseBindMatricesAccessor()
{
if (!this._inverseBindMatrices.HasValue) return null;
return this.LogicalParent.LogicalAccessors[this._inverseBindMatrices.Value];
}
public (Node Joint, Matrix4x4 InverseBindMatrix) GetJoint(int idx)
{
var nodeIdx = _joints[idx];
var node = this.LogicalParent.LogicalNodes[nodeIdx];
var matrices = GetInverseBindMatricesAccessor();
var matrix = matrices == null ? Matrix4x4.Identity : matrices.AsMatrix4x4Array()[idx];
return (node, matrix);
}
public void BindJoints(params Node[] joints)
{
var rootJoint = _FindCommonAncestor(joints);
BindJoints(rootJoint.WorldMatrix, joints);
}
/// <summary>
/// Binds a bone armature of <see cref="Node"/> to the associated skinned mesh.
/// </summary>
/// <param name="meshBindTransform">The world transform matrix of the mesh at the time of binding.</param>
/// <param name="joints">A collection of <see cref="Node"/> joints.</param>
/// <remarks>
/// This method uses the <see cref="Node.WorldMatrix"/> value of each joint to computer the inverse bind matrix.
/// </remarks>
public void BindJoints(Matrix4x4 meshBindTransform, params Node[] joints)
{
Guard.NotNull(joints, nameof(joints));
var pairs = new (Node, Matrix4x4)[joints.Length];
for (int i = 0; i < pairs.Length; ++i)
{
Guard.NotNull(joints[i], nameof(joints));
var xform = (Matrix4x4)Transforms.SkinnedTransform.CalculateInverseBinding((Transforms.Matrix4x4Double)meshBindTransform, joints[i].WorldMatrixPrecise);
pairs[i] = (joints[i], xform);
}
BindJoints(pairs);
}
/// <summary>
/// Binds a bone armature of <see cref="Node"/> to the associated skinned mesh.
/// </summary>
/// <param name="joints">
/// A collection of <see cref="Node"/> joints,
/// where each joint has an Inverse Bind Matrix.
/// </param>
public void BindJoints((Node Joint, Matrix4x4 InverseBindMatrix)[] joints)
{
Guard.NotNull(joints, nameof(joints));
_FindCommonAncestor(joints.Select(item => item.Joint));
foreach (var j in joints) { Guard.IsTrue(j.InverseBindMatrix._IsFinite(), 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.InverseBindMatrix));
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.Joint.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>
internal bool IsMatch(Node skeleton, KeyValuePair<Node, Matrix4x4>[] joints)
{
if (!ReferenceEquals(skeleton, this.Skeleton)) return false;
if (joints.Length != this._joints.Count) return false;
for (int i = 0; i < this._joints.Count; ++i)
{
var src = joints[i];
var (j, ibm) = GetJoint(i);
if (!ReferenceEquals(src.Key, j)) return false;
if (src.Value != ibm) return false;
}
return true;
}
/// <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);
}
/// <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)
{
if (nodes == null) return null;
foreach (var j in nodes)
{
Guard.NotNull(j, nameof(nodes));
Guard.MustShareLogicalParent(this, j, nameof(nodes));
}
var workingNodes = nodes.ToList();
var rootJoint = workingNodes.First();
while (true)
{
if (workingNodes.All(j => rootJoint == j || rootJoint._ContainsVisualNode(j, true))) return rootJoint;
if (rootJoint.VisualParent == null) break;
rootJoint = rootJoint.VisualParent;
}
Guard.IsTrue(false, nameof(nodes), "Common ancestor not found");
return null;
}
#endregion
#region validation
protected override void OnValidateReferences(Validation.ValidationContext validate)
{
validate
.IsNullOrIndex("Skeleton", _skeleton, this.LogicalParent.LogicalNodes)
.IsNullOrIndex("InverseBindMatrices", _inverseBindMatrices, this.LogicalParent.LogicalAccessors)
.IsGreaterOrEqual("Joints", _joints.Count, _jointsMinItems); // althought mathematically correct, there's no point in having a skin with just one node.
var ibxAccessor = GetInverseBindMatricesAccessor();
if (ibxAccessor != null) validate.AreEqual("InverseBindMatrices", ibxAccessor.Count, _joints.Count);
Node commonRoot = null;
for (int i = 0; i < _joints.Count; ++i)
{
var jidx = _joints[i];
validate.IsNullOrIndex("Joints", jidx, this.LogicalParent.LogicalNodes);
var jnode = this.LogicalParent.LogicalNodes[jidx];
var jroot = jnode.VisualRoot;
if (commonRoot == null) { commonRoot = jroot; continue; }
validate.GetContext(jroot).AreSameReference("Root", commonRoot, jroot);
}
base.OnValidateReferences(validate);
}
protected override void OnValidateContent(Validation.ValidationContext validate)
{
var ibxAccessor = GetInverseBindMatricesAccessor();
if (ibxAccessor != null)
{
validate.AreEqual("InverseBindMatrices", _joints.Count, ibxAccessor.Count);
ibxAccessor.ValidateMatrices(validate, false, true);
}
if (_skeleton.HasValue)
{
var skeletonNode = this.Skeleton;
for (int i = 0; i < this.JointsCount; ++i)
{
var jointNode = GetJoint(i).Joint;
if (skeletonNode == jointNode) continue;
if (skeletonNode._ContainsVisualNode(jointNode, true)) continue;
// result.AddError(this, $"Skeleton node is not a common ancestor of Joint[{i}]");
}
}
base.OnValidateContent(validate);
}
#endregion
}
public partial class ModelRoot
{
/// <summary>
/// Creates a new <see cref="Skin"/> instance
/// and adds it to <see cref="ModelRoot.LogicalSkins"/>.
/// </summary>
/// <param name="name">The name of the instance.</param>
/// <returns>A <see cref="Skin"/> instance.</returns>
public Skin CreateSkin(string name = null)
{
var skin = new Skin();
skin.Name = name;
this._skins.Add(skin);
return skin;
}
}
}