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
{
///
/// Interface for a mesh transform object
///
public interface ITransform
{
///
/// Gets a value indicating whether the current will render visible geometry.
///
bool Visible { get; }
///
/// Gets a value indicating whether the triangles need to be flipped to render correctly.
///
bool FlipFaces { get; }
V3 TransformPosition(V3 position, V3[] morphTargets, (int, float)[] skinWeights);
V3 TransformNormal(V3 normal, V3[] morphTargets, (int, float)[] skinWeights);
V4 TransformTangent(V4 tangent, V3[] morphTargets, (int, float)[] skinWeights);
V4 MorphColors(V4 color, V4[] morphTargets);
}
public abstract class MorphTransform
{
#region constructor
protected MorphTransform()
{
Update(SparseWeight8.Create((0, 1)), false);
}
protected MorphTransform(SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
{
Update(morphWeights, useAbsoluteMorphTargets);
}
#endregion
#region data
///
/// Represents a sparse collection of weights where:
/// - Index of value points to the Mesh master positions.
/// - All other indices point to Mesh MorphTarget[index] positions.
///
private SparseWeight8 _Weights;
private const int _COMPLEMENT_INDEX = 65536;
///
/// True if morph targets represent absolute values.
/// False if morph targets represent values relative to master value.
///
private bool _AbsoluteMorphTargets;
#endregion
#region API
public void Update(SparseWeight8 morphWeights, bool useAbsoluteMorphTargets = false)
{
_AbsoluteMorphTargets = useAbsoluteMorphTargets;
if (morphWeights.IsWeightless)
{
_Weights = SparseWeight8.Create((0, 1));
return;
}
_Weights = morphWeights.GetNormalizedWithComplement(_COMPLEMENT_INDEX);
}
protected V3 MorphVectors(V3 value, V3[] morphTargets)
{
if (morphTargets == null) return value;
if (_Weights.Index0 == _COMPLEMENT_INDEX && _Weights.Weight0 == 1) return value;
var p = V3.Zero;
if (_AbsoluteMorphTargets)
{
foreach (var pair in _Weights.GetNonZeroWeights())
{
var val = pair.Item1 == _COMPLEMENT_INDEX ? value : morphTargets[pair.Item1];
p += val * pair.Item2;
}
}
else
{
foreach (var pair in _Weights.GetNonZeroWeights())
{
var val = pair.Item1 == _COMPLEMENT_INDEX ? value : value + morphTargets[pair.Item1];
p += val * pair.Item2;
}
}
return p;
}
protected V4 MorphVectors(V4 value, V4[] morphTargets)
{
if (morphTargets == null) 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.Item1 == _COMPLEMENT_INDEX ? value : morphTargets[pair.Item1];
p += val * pair.Item2;
}
}
else
{
foreach (var pair in _Weights.GetNonZeroWeights())
{
var val = pair.Item1 == _COMPLEMENT_INDEX ? value : value + morphTargets[pair.Item1];
p += val * pair.Item2;
}
}
return p;
}
public V4 MorphColors(V4 color, V4[] morphTargets)
{
return MorphVectors(color, morphTargets);
}
#endregion
}
public class StaticTransform : MorphTransform, ITransform
{
#region constructor
public StaticTransform(TRANSFORM xform, SparseWeight8 morphWeights, bool useAbsoluteMorphs)
{
Update(xform, morphWeights, useAbsoluteMorphs);
}
#endregion
#region data
private TRANSFORM _Transform;
private Boolean _Visible;
private Boolean _FlipFaces;
#endregion
#region properties
public Boolean Visible => _Visible;
public Boolean FlipFaces => _FlipFaces;
#endregion
#region API
public void Update(TRANSFORM xform, SparseWeight8 morphWeights, bool useAbsoluteMorphs)
{
Update(morphWeights, useAbsoluteMorphs);
_Transform = xform;
// http://m-hikari.com/ija/ija-password-2009/ija-password5-8-2009/hajrizajIJA5-8-2009.pdf
float determinant3x3 =
+(xform.M13 * xform.M21 * xform.M32)
+ (xform.M11 * xform.M22 * xform.M33)
+ (xform.M12 * xform.M23 * xform.M31)
- (xform.M12 * xform.M21 * xform.M33)
- (xform.M13 * xform.M22 * xform.M31)
- (xform.M11 * xform.M23 * xform.M32);
_Visible = Math.Abs(determinant3x3) > float.Epsilon;
_FlipFaces = determinant3x3 < 0;
}
public V3 TransformPosition(V3 position, V3[] morphTargets, (int, float)[] skinWeights)
{
position = MorphVectors(position, morphTargets);
return V3.Transform(position, _Transform);
}
public V3 TransformNormal(V3 normal, V3[] morphTargets, (int, float)[] skinWeights)
{
normal = MorphVectors(normal, morphTargets);
return V3.Normalize(V3.Transform(normal, _Transform));
}
public V4 TransformTangent(V4 tangent, V3[] morphTargets, (int, float)[] skinWeights)
{
var n = MorphVectors(new V3(tangent.X, tangent.Y, tangent.Z), morphTargets);
n = V3.Normalize(V3.Transform(n, _Transform));
return new V4(n, tangent.W);
}
#endregion
}
public class SkinTransform : MorphTransform, ITransform
{
#region constructor
public SkinTransform(TRANSFORM[] invBindings, TRANSFORM[] worldXforms, SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
{
Update(invBindings, worldXforms, morphWeights, useAbsoluteMorphTargets);
}
#endregion
#region data
private TRANSFORM[] _JointTransforms;
#endregion
#region API
public void Update(TRANSFORM[] invBindings, TRANSFORM[] worldXforms, SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
{
Guard.NotNull(invBindings, nameof(invBindings));
Guard.NotNull(worldXforms, nameof(worldXforms));
Guard.IsTrue(invBindings.Length == worldXforms.Length, nameof(worldXforms), $"{invBindings} and {worldXforms} length mismatch.");
Update(morphWeights, useAbsoluteMorphTargets);
if (_JointTransforms == null || _JointTransforms.Length != invBindings.Length) _JointTransforms = new TRANSFORM[invBindings.Length];
for (int i = 0; i < _JointTransforms.Length; ++i)
{
_JointTransforms[i] = invBindings[i] * worldXforms[i];
}
}
public bool Visible => true;
public bool FlipFaces => false;
public V3 TransformPosition(V3 localPosition, V3[] morphTargets, (int, float)[] skinWeights)
{
Guard.NotNull(skinWeights, nameof(skinWeights));
localPosition = MorphVectors(localPosition, morphTargets);
var worldPosition = V3.Zero;
var wnrm = 1.0f / skinWeights.Sum(item => item.Item2);
foreach (var jw in skinWeights)
{
worldPosition += V3.Transform(localPosition, _JointTransforms[jw.Item1]) * jw.Item2 * wnrm;
}
return worldPosition;
}
public V3 TransformNormal(V3 localNormal, V3[] morphTargets, (int, float)[] skinWeights)
{
Guard.NotNull(skinWeights, nameof(skinWeights));
localNormal = MorphVectors(localNormal, morphTargets);
var worldNormal = V3.Zero;
foreach (var jw in skinWeights)
{
worldNormal += V3.TransformNormal(localNormal, _JointTransforms[jw.Item1]) * jw.Item2;
}
return V3.Normalize(localNormal);
}
public V4 TransformTangent(V4 localTangent, V3[] morphTargets, (int, float)[] skinWeights)
{
Guard.NotNull(skinWeights, nameof(skinWeights));
var localTangentV = MorphVectors(new V3(localTangent.X, localTangent.Y, localTangent.Z), morphTargets);
var worldTangent = V3.Zero;
foreach (var jw in skinWeights)
{
worldTangent += V3.TransformNormal(localTangentV, _JointTransforms[jw.Item1]) * jw.Item2;
}
worldTangent = V3.Normalize(worldTangent);
return new V4(worldTangent, localTangent.W);
}
#endregion
#region helper utilities
///
/// Calculates the inverse bind matrix to use for runtime skinning.
///
/// The world space of the mesh at the time of binding (POSE).
/// The world space of the given bone joint at the time of binding (POSE).
/// A representing the inverse bind transform.
public static Matrix4x4 CalculateInverseBinding(Matrix4x4 meshWorldTransform, Matrix4x4 jointWorldTransform)
{
var xform = meshWorldTransform.Inverse();
xform = jointWorldTransform * xform;
return xform.Inverse();
}
#endregion
}
}