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 IGeometryTransform
{
///
/// Gets a value indicating whether the current will render visible geometry.
///
///
/// When this value is false, a runtime should skip rendering any geometry using
/// this instance, since it will not be visible anyway.
///
bool Visible { get; }
///
/// Gets a value indicating whether the triangles need to be flipped to render correctly.
///
///
/// When this value is true, a runtime rendering triangles should inverse the face culling.
///
bool FlipFaces { get; }
V3 TransformPosition(V3 position, V3[] morphTargets, in SparseWeight8 skinWeights);
V3 TransformNormal(V3 normal, V3[] morphTargets, in SparseWeight8 skinWeights);
V4 TransformTangent(V4 tangent, V3[] morphTargets, in SparseWeight8 skinWeights);
V4 MorphColors(V4 color, 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
///
/// 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;
public 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 properties
///
/// Gets the current morph weights to use for morph target blending. represents the index for the base geometry.
///
public SparseWeight8 MorphWeights => _Weights;
///
/// Gets a value indicating whether morph target values are absolute, and not relative to the master value.
///
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, V3[] morphTargets)
{
if (_Weights.Index0 == COMPLEMENT_INDEX && _Weights.Weight0 == 1) return value;
if (morphTargets == null) 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 (_Weights.Index0 == COMPLEMENT_INDEX && _Weights.Weight0 == 1) return value;
if (morphTargets == null) 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, IGeometryTransform
{
#region constructor
public StaticTransform()
{
Update(TRANSFORM.Identity);
}
public StaticTransform(TRANSFORM worldMatrix)
{
Update(default, false);
Update(worldMatrix);
}
public StaticTransform(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 Matrix4x4 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, V3[] morphTargets, in SparseWeight8 skinWeights)
{
position = MorphVectors(position, morphTargets);
return V3.Transform(position, _WorldMatrix);
}
public V3 TransformNormal(V3 normal, V3[] morphTargets, in SparseWeight8 skinWeights)
{
normal = MorphVectors(normal, morphTargets);
return V3.Normalize(V3.Transform(normal, _WorldMatrix));
}
public V4 TransformTangent(V4 tangent, V3[] morphTargets, in SparseWeight8 skinWeights)
{
var n = MorphVectors(new V3(tangent.X, tangent.Y, tangent.Z), morphTargets);
n = V3.Normalize(V3.Transform(n, _WorldMatrix));
return new V4(n, tangent.W);
}
#endregion
}
public class SkinTransform : MorphTransform, IGeometryTransform
{
#region constructor
public SkinTransform() { }
public SkinTransform(TRANSFORM[] invBindMatrix, TRANSFORM[] currWorldMatrix, SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
{
Update(morphWeights, useAbsoluteMorphTargets);
Update(invBindMatrix, currWorldMatrix);
}
public SkinTransform(int count, Func invBindMatrix, Func currWorldMatrix, SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
{
Update(morphWeights, useAbsoluteMorphTargets);
Update(count, invBindMatrix, currWorldMatrix);
}
#endregion
#region data
private TRANSFORM[] _SkinTransforms;
#endregion
#region properties
///
/// Gets the collection of the current, final matrices to use for skinning
///
public IReadOnlyList 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 invBindMatrix, Func 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 bool Visible => true;
public bool FlipFaces => false;
public V3 TransformPosition(V3 localPosition, V3[] morphTargets, in SparseWeight8 skinWeights)
{
Guard.NotNull(skinWeights, nameof(skinWeights));
localPosition = MorphVectors(localPosition, morphTargets);
var worldPosition = V3.Zero;
var wnrm = 1.0f / skinWeights.WeightSum;
foreach (var jw in skinWeights.GetIndexedWeights())
{
worldPosition += V3.Transform(localPosition, _SkinTransforms[jw.Item1]) * jw.Item2 * wnrm;
}
return worldPosition;
}
public V3 TransformNormal(V3 localNormal, V3[] morphTargets, in SparseWeight8 skinWeights)
{
Guard.NotNull(skinWeights, nameof(skinWeights));
localNormal = MorphVectors(localNormal, morphTargets);
var worldNormal = V3.Zero;
foreach (var jw in skinWeights.GetIndexedWeights())
{
worldNormal += V3.TransformNormal(localNormal, _SkinTransforms[jw.Item1]) * jw.Item2;
}
return V3.Normalize(localNormal);
}
public V4 TransformTangent(V4 localTangent, V3[] morphTargets, in SparseWeight8 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.GetIndexedWeights())
{
worldTangent += V3.TransformNormal(localTangentV, _SkinTransforms[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 TRANSFORM CalculateInverseBinding(TRANSFORM meshWorldTransform, TRANSFORM jointWorldTransform)
{
// var xform = meshWorldTransform.Inverse();
// xform = jointWorldTransform * xform;
// return xform.Inverse();
var invJoint = jointWorldTransform.Inverse();
return meshWorldTransform * invJoint;
}
#endregion
}
}