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;
using System.Text;
using TRANSFORM = System.Numerics.Matrix4x4;
using V3 = System.Numerics.Vector3;
using V4 = System.Numerics.Vector4;
namespace SharpGLTF.Transforms
{
/// <summary>
/// Interface for a mesh transform object
/// </summary>
public interface IGeometryTransform
{
/// <summary>
/// Gets a value indicating whether the current <see cref="IGeometryTransform"/> will render visible geometry.
/// </summary>
/// <remarks>
/// When this value is false, a runtime should skip rendering any geometry using
/// this <see cref="IGeometryTransform"/> instance, since it will not be visible anyway.
/// </remarks>
bool Visible { get; }
/// <summary>
/// Gets a value indicating whether the triangles need to be flipped to render correctly.
/// </summary>
/// <remarks>
/// When this value is true, a runtime rendering triangles should inverse the face culling.
/// </remarks>
bool FlipFaces { get; }
/// <summary>
/// Transforms a vertex position from local mesh space to world space.
/// </summary>
/// <param name="position">The local position of the vertex.</param>
/// <param name="positionDeltas">The local position deltas of the vertex, one for each morph target, or null.</param>
/// <param name="skinWeights">The skin weights of the vertex, or default.</param>
/// <returns>A position in world space.</returns>
V3 TransformPosition(V3 position, IReadOnlyList<V3> positionDeltas, in SparseWeight8 skinWeights);
/// <summary>
/// Transforms a vertex normal from local mesh space to world space.
/// </summary>
/// <param name="normal">The local normal of the vertex.</param>
/// <param name="normalDeltas">The local normal deltas of the vertex, one for each morph target, or null.</param>
/// <param name="skinWeights">The skin weights of the vertex, or default.</param>
/// <returns>A normal in world space.</returns>
V3 TransformNormal(V3 normal, IReadOnlyList<V3> normalDeltas, in SparseWeight8 skinWeights);
/// <summary>
/// Transforms a vertex tangent from local mesh space to world space.
/// </summary>
/// <param name="tangent">The tangent normal of the vertex.</param>
/// <param name="tangentDeltas">The local tangent deltas of the vertex, one for each morph target, or null.</param>
/// <param name="skinWeights">The skin weights of the vertex, or default.</param>
/// <returns>A tangent in world space.</returns>
V4 TransformTangent(V4 tangent, IReadOnlyList<V3> tangentDeltas, in SparseWeight8 skinWeights);
V4 MorphColors(V4 color, IReadOnlyList<V4> morphTargets);
}
public abstract class MorphTransform
{
#region constructor
protected MorphTransform()
{
Update(default, false);
}
protected MorphTransform(SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
{
Update(morphWeights, useAbsoluteMorphTargets);
}
#endregion
#region data
/// <summary>
/// Represents a sparse collection of weights where:
/// - Index of value <see cref="COMPLEMENT_INDEX"/> points to the Mesh master positions.
/// - All other indices point to Mesh MorphTarget[index] positions.
/// </summary>
private SparseWeight8 _Weights;
public const int COMPLEMENT_INDEX = 65536;
/// <summary>
/// True if morph targets represent absolute values.
/// False if morph targets represent values relative to master value.
/// </summary>
private bool _AbsoluteMorphTargets;
#endregion
#region properties
/// <summary>
/// Gets the current morph weights to use for morph target blending. <see cref="COMPLEMENT_INDEX"/> represents the index for the base geometry.
/// </summary>
public SparseWeight8 MorphWeights => _Weights;
/// <summary>
/// Gets a value indicating whether morph target values are absolute, and not relative to the master value.
/// </summary>
public bool AbsoluteMorphTargets => _AbsoluteMorphTargets;
#endregion
#region API
public void Update(SparseWeight8 morphWeights, bool useAbsoluteMorphTargets = false)
{
_AbsoluteMorphTargets = useAbsoluteMorphTargets;
if (morphWeights.IsWeightless)
{
_Weights = SparseWeight8.Create((COMPLEMENT_INDEX, 1));
return;
}
_Weights = morphWeights.GetNormalizedWithComplement(COMPLEMENT_INDEX);
}
protected V3 MorphVectors(V3 value, IReadOnlyList<V3> morphTargets)
{
if (morphTargets == null || morphTargets.Count == 0) return value;
if (_Weights.Index0 == COMPLEMENT_INDEX && _Weights.Weight0 == 1) return value;
var p = V3.Zero;
if (_AbsoluteMorphTargets)
{
foreach (var (index, weight) in _Weights.GetNonZeroWeights())
{
var val = index == COMPLEMENT_INDEX ? value : morphTargets[index];
p += val * weight;
}
}
else
{
foreach (var (index, weight) in _Weights.GetNonZeroWeights())
{
var val = index == COMPLEMENT_INDEX ? value : value + morphTargets[index];
p += val * weight;
}
}
return p;
}
protected V4 MorphVectors(V4 value, IReadOnlyList<V4> morphTargets)
{
if (morphTargets == null || morphTargets.Count == 0) return value;
if (_Weights.Index0 == COMPLEMENT_INDEX && _Weights.Weight0 == 1) return value;
var p = V4.Zero;
if (_AbsoluteMorphTargets)
{
foreach (var pair in _Weights.GetNonZeroWeights())
{
var val = pair.Index == COMPLEMENT_INDEX ? value : morphTargets[pair.Index];
p += val * pair.Weight;
}
}
else
{
foreach (var pair in _Weights.GetNonZeroWeights())
{
var val = pair.Index == COMPLEMENT_INDEX ? value : value + morphTargets[pair.Index];
p += val * pair.Weight;
}
}
return p;
}
public V4 MorphColors(V4 color, IReadOnlyList<V4> morphTargets)
{
return MorphVectors(color, morphTargets);
}
#endregion
}
public class RigidTransform : MorphTransform, IGeometryTransform
{
#region constructor
public RigidTransform()
{
Update(TRANSFORM.Identity);
}
public RigidTransform(TRANSFORM worldMatrix)
{
Update(default, false);
Update(worldMatrix);
}
public RigidTransform(TRANSFORM worldMatrix, SparseWeight8 morphWeights, bool useAbsoluteMorphs)
{
Update(morphWeights, useAbsoluteMorphs);
Update(worldMatrix);
}
#endregion
#region data
private TRANSFORM _WorldMatrix;
private Boolean _Visible;
private Boolean _FlipFaces;
#endregion
#region properties
public Boolean Visible => _Visible;
public Boolean FlipFaces => _FlipFaces;
public TRANSFORM WorldMatrix => _WorldMatrix;
#endregion
#region API
public void Update(TRANSFORM worldMatrix)
{
_WorldMatrix = worldMatrix;
// http://m-hikari.com/ija/ija-password-2009/ija-password5-8-2009/hajrizajIJA5-8-2009.pdf
float determinant3x3 =
+(worldMatrix.M13 * worldMatrix.M21 * worldMatrix.M32)
+ (worldMatrix.M11 * worldMatrix.M22 * worldMatrix.M33)
+ (worldMatrix.M12 * worldMatrix.M23 * worldMatrix.M31)
- (worldMatrix.M12 * worldMatrix.M21 * worldMatrix.M33)
- (worldMatrix.M13 * worldMatrix.M22 * worldMatrix.M31)
- (worldMatrix.M11 * worldMatrix.M23 * worldMatrix.M32);
_Visible = Math.Abs(determinant3x3) > float.Epsilon;
_FlipFaces = determinant3x3 < 0;
}
public V3 TransformPosition(V3 position, IReadOnlyList<V3> morphTargets, in SparseWeight8 skinWeights)
{
position = MorphVectors(position, morphTargets);
return V3.Transform(position, _WorldMatrix);
}
public V3 TransformNormal(V3 normal, IReadOnlyList<V3> morphTargets, in SparseWeight8 skinWeights)
{
normal = MorphVectors(normal, morphTargets);
return V3.Normalize(V3.TransformNormal(normal, _WorldMatrix));
}
public V4 TransformTangent(V4 tangent, IReadOnlyList<V3> morphTargets, in SparseWeight8 skinWeights)
{
var t = MorphVectors(new V3(tangent.X, tangent.Y, tangent.Z), morphTargets);
t = V3.Normalize(V3.TransformNormal(t, _WorldMatrix));
return new V4(t, tangent.W);
}
#endregion
}
public class SkinnedTransform : MorphTransform, IGeometryTransform
{
#region constructor
public SkinnedTransform() { }
public SkinnedTransform(TRANSFORM[] invBindMatrix, TRANSFORM[] currWorldMatrix, SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
{
Update(morphWeights, useAbsoluteMorphTargets);
Update(invBindMatrix, currWorldMatrix);
}
public SkinnedTransform(int count, Func<int, TRANSFORM> invBindMatrix, Func<int, TRANSFORM> currWorldMatrix, SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
{
Update(morphWeights, useAbsoluteMorphTargets);
Update(count, invBindMatrix, currWorldMatrix);
}
#endregion
#region data
private TRANSFORM[] _SkinTransforms;
#endregion
#region properties
public bool Visible => true;
public bool FlipFaces => false;
/// <summary>
/// Gets the collection of the current, final matrices to use for skinning
/// </summary>
public IReadOnlyList<TRANSFORM> SkinMatrices => _SkinTransforms;
#endregion
#region API
public void Update(TRANSFORM[] invBindMatrix, TRANSFORM[] currWorldMatrix)
{
Guard.NotNull(invBindMatrix, nameof(invBindMatrix));
Guard.NotNull(currWorldMatrix, nameof(currWorldMatrix));
Guard.IsTrue(invBindMatrix.Length == currWorldMatrix.Length, nameof(currWorldMatrix), $"{invBindMatrix} and {currWorldMatrix} length mismatch.");
if (_SkinTransforms == null || _SkinTransforms.Length != invBindMatrix.Length) _SkinTransforms = new TRANSFORM[invBindMatrix.Length];
for (int i = 0; i < _SkinTransforms.Length; ++i)
{
_SkinTransforms[i] = invBindMatrix[i] * currWorldMatrix[i];
}
}
public void Update(int count, Func<int, TRANSFORM> invBindMatrix, Func<int, TRANSFORM> currWorldMatrix)
{
Guard.NotNull(invBindMatrix, nameof(invBindMatrix));
Guard.NotNull(currWorldMatrix, nameof(currWorldMatrix));
if (_SkinTransforms == null || _SkinTransforms.Length != count) _SkinTransforms = new TRANSFORM[count];
for (int i = 0; i < _SkinTransforms.Length; ++i)
{
_SkinTransforms[i] = invBindMatrix(i) * currWorldMatrix(i);
}
}
public V3 TransformPosition(V3 localPosition, IReadOnlyList<V3> morphTargets, in SparseWeight8 skinWeights)
{
localPosition = MorphVectors(localPosition, morphTargets);
var worldPosition = V3.Zero;
var wnrm = 1.0f / skinWeights.WeightSum;
foreach (var (jidx, jweight) in skinWeights.GetIndexedWeights())
{
worldPosition += V3.Transform(localPosition, _SkinTransforms[jidx]) * jweight * wnrm;
}
return worldPosition;
}
public V3 TransformNormal(V3 localNormal, IReadOnlyList<V3> morphTargets, in SparseWeight8 skinWeights)
{
localNormal = MorphVectors(localNormal, morphTargets);
var worldNormal = V3.Zero;
foreach (var (jidx, jweight) in skinWeights.GetIndexedWeights())
{
worldNormal += V3.TransformNormal(localNormal, _SkinTransforms[jidx]) * jweight;
}
return V3.Normalize(localNormal);
}
public V4 TransformTangent(V4 localTangent, IReadOnlyList<V3> morphTargets, in SparseWeight8 skinWeights)
{
var localTangentV = MorphVectors(new V3(localTangent.X, localTangent.Y, localTangent.Z), morphTargets);
var worldTangent = V3.Zero;
foreach (var (jidx, jweight) in skinWeights.GetIndexedWeights())
{
worldTangent += V3.TransformNormal(localTangentV, _SkinTransforms[jidx]) * jweight;
}
worldTangent = V3.Normalize(worldTangent);
return new V4(worldTangent, localTangent.W);
}
#endregion
#region helper utilities
/// <summary>
/// Calculates the inverse bind matrix to use for runtime skinning.
/// </summary>
/// <param name="meshWorldTransform">The world space <see cref="TRANSFORM"/> of the mesh at the time of binding (POSE).</param>
/// <param name="jointWorldTransform">The world space <see cref="TRANSFORM"/> of the given bone joint at the time of binding (POSE).</param>
/// <returns>A <see cref="TRANSFORM"/> representing the inverse bind transform.</returns>
public static TRANSFORM CalculateInverseBinding(TRANSFORM meshWorldTransform, TRANSFORM jointWorldTransform)
{
var invJoint = jointWorldTransform.Inverse();
if (meshWorldTransform == TRANSFORM.Identity) return invJoint;
return meshWorldTransform * invJoint;
}
public static Matrix4x4Double CalculateInverseBinding(Matrix4x4Double meshWorldTransform, Matrix4x4Double jointWorldTransform)
{
if (!Matrix4x4Double.Invert(jointWorldTransform, out Matrix4x4Double invJoint)) Guard.IsTrue(false, nameof(jointWorldTransform), "Matrix cannot be inverted.");
if (meshWorldTransform == Matrix4x4Double.Identity) return invJoint;
return meshWorldTransform * invJoint;
}
#endregion
}
}