| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- 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; }
- 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
- /// <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, 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<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
- /// <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 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
- /// <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 xform = meshWorldTransform.Inverse();
- // xform = jointWorldTransform * xform;
- // return xform.Inverse();
- var invJoint = jointWorldTransform.Inverse();
- return meshWorldTransform * invJoint;
- }
- #endregion
- }
- }
|