MeshTransforms.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Numerics;
  5. using System.Text;
  6. using TRANSFORM = System.Numerics.Matrix4x4;
  7. using V3 = System.Numerics.Vector3;
  8. using V4 = System.Numerics.Vector4;
  9. namespace SharpGLTF.Transforms
  10. {
  11. /// <summary>
  12. /// Interface for a mesh transform object
  13. /// </summary>
  14. public interface IGeometryTransform
  15. {
  16. /// <summary>
  17. /// Gets a value indicating whether the current <see cref="IGeometryTransform"/> will render visible geometry.
  18. /// </summary>
  19. /// <remarks>
  20. /// When this value is false, a runtime should skip rendering any geometry using
  21. /// this <see cref="IGeometryTransform"/> instance, since it will not be visible anyway.
  22. /// </remarks>
  23. bool Visible { get; }
  24. /// <summary>
  25. /// Gets a value indicating whether the triangles need to be flipped to render correctly.
  26. /// </summary>
  27. /// <remarks>
  28. /// When this value is true, a runtime rendering triangles should inverse the face culling.
  29. /// </remarks>
  30. bool FlipFaces { get; }
  31. V3 TransformPosition(V3 position, V3[] morphTargets, in SparseWeight8 skinWeights);
  32. V3 TransformNormal(V3 normal, V3[] morphTargets, in SparseWeight8 skinWeights);
  33. V4 TransformTangent(V4 tangent, V3[] morphTargets, in SparseWeight8 skinWeights);
  34. V4 MorphColors(V4 color, V4[] morphTargets);
  35. }
  36. public abstract class MorphTransform
  37. {
  38. #region constructor
  39. protected MorphTransform()
  40. {
  41. Update(default, false);
  42. }
  43. protected MorphTransform(SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
  44. {
  45. Update(morphWeights, useAbsoluteMorphTargets);
  46. }
  47. #endregion
  48. #region data
  49. /// <summary>
  50. /// Represents a sparse collection of weights where:
  51. /// - Index of value <see cref="COMPLEMENT_INDEX"/> points to the Mesh master positions.
  52. /// - All other indices point to Mesh MorphTarget[index] positions.
  53. /// </summary>
  54. private SparseWeight8 _Weights;
  55. public const int COMPLEMENT_INDEX = 65536;
  56. /// <summary>
  57. /// True if morph targets represent absolute values.
  58. /// False if morph targets represent values relative to master value.
  59. /// </summary>
  60. private bool _AbsoluteMorphTargets;
  61. #endregion
  62. #region properties
  63. /// <summary>
  64. /// Gets the current morph weights to use for morph target blending. <see cref="COMPLEMENT_INDEX"/> represents the index for the base geometry.
  65. /// </summary>
  66. public SparseWeight8 MorphWeights => _Weights;
  67. /// <summary>
  68. /// Gets a value indicating whether morph target values are absolute, and not relative to the master value.
  69. /// </summary>
  70. public bool AbsoluteMorphTargets => _AbsoluteMorphTargets;
  71. #endregion
  72. #region API
  73. public void Update(SparseWeight8 morphWeights, bool useAbsoluteMorphTargets = false)
  74. {
  75. _AbsoluteMorphTargets = useAbsoluteMorphTargets;
  76. if (morphWeights.IsWeightless)
  77. {
  78. _Weights = SparseWeight8.Create((COMPLEMENT_INDEX, 1));
  79. return;
  80. }
  81. _Weights = morphWeights.GetNormalizedWithComplement(COMPLEMENT_INDEX);
  82. }
  83. protected V3 MorphVectors(V3 value, V3[] morphTargets)
  84. {
  85. if (_Weights.Index0 == COMPLEMENT_INDEX && _Weights.Weight0 == 1) return value;
  86. if (morphTargets == null) return value;
  87. var p = V3.Zero;
  88. if (_AbsoluteMorphTargets)
  89. {
  90. foreach (var pair in _Weights.GetNonZeroWeights())
  91. {
  92. var val = pair.Item1 == COMPLEMENT_INDEX ? value : morphTargets[pair.Item1];
  93. p += val * pair.Item2;
  94. }
  95. }
  96. else
  97. {
  98. foreach (var pair in _Weights.GetNonZeroWeights())
  99. {
  100. var val = pair.Item1 == COMPLEMENT_INDEX ? value : value + morphTargets[pair.Item1];
  101. p += val * pair.Item2;
  102. }
  103. }
  104. return p;
  105. }
  106. protected V4 MorphVectors(V4 value, V4[] morphTargets)
  107. {
  108. if (_Weights.Index0 == COMPLEMENT_INDEX && _Weights.Weight0 == 1) return value;
  109. if (morphTargets == null) return value;
  110. var p = V4.Zero;
  111. if (_AbsoluteMorphTargets)
  112. {
  113. foreach (var pair in _Weights.GetNonZeroWeights())
  114. {
  115. var val = pair.Item1 == COMPLEMENT_INDEX ? value : morphTargets[pair.Item1];
  116. p += val * pair.Item2;
  117. }
  118. }
  119. else
  120. {
  121. foreach (var pair in _Weights.GetNonZeroWeights())
  122. {
  123. var val = pair.Item1 == COMPLEMENT_INDEX ? value : value + morphTargets[pair.Item1];
  124. p += val * pair.Item2;
  125. }
  126. }
  127. return p;
  128. }
  129. public V4 MorphColors(V4 color, V4[] morphTargets)
  130. {
  131. return MorphVectors(color, morphTargets);
  132. }
  133. #endregion
  134. }
  135. public class StaticTransform : MorphTransform, IGeometryTransform
  136. {
  137. #region constructor
  138. public StaticTransform()
  139. {
  140. Update(TRANSFORM.Identity);
  141. }
  142. public StaticTransform(TRANSFORM worldMatrix)
  143. {
  144. Update(default, false);
  145. Update(worldMatrix);
  146. }
  147. public StaticTransform(TRANSFORM worldMatrix, SparseWeight8 morphWeights, bool useAbsoluteMorphs)
  148. {
  149. Update(morphWeights, useAbsoluteMorphs);
  150. Update(worldMatrix);
  151. }
  152. #endregion
  153. #region data
  154. private TRANSFORM _WorldMatrix;
  155. private Boolean _Visible;
  156. private Boolean _FlipFaces;
  157. #endregion
  158. #region properties
  159. public Boolean Visible => _Visible;
  160. public Boolean FlipFaces => _FlipFaces;
  161. public Matrix4x4 WorldMatrix => _WorldMatrix;
  162. #endregion
  163. #region API
  164. public void Update(TRANSFORM worldMatrix)
  165. {
  166. _WorldMatrix = worldMatrix;
  167. // http://m-hikari.com/ija/ija-password-2009/ija-password5-8-2009/hajrizajIJA5-8-2009.pdf
  168. float determinant3x3 =
  169. +(worldMatrix.M13 * worldMatrix.M21 * worldMatrix.M32)
  170. + (worldMatrix.M11 * worldMatrix.M22 * worldMatrix.M33)
  171. + (worldMatrix.M12 * worldMatrix.M23 * worldMatrix.M31)
  172. - (worldMatrix.M12 * worldMatrix.M21 * worldMatrix.M33)
  173. - (worldMatrix.M13 * worldMatrix.M22 * worldMatrix.M31)
  174. - (worldMatrix.M11 * worldMatrix.M23 * worldMatrix.M32);
  175. _Visible = Math.Abs(determinant3x3) > float.Epsilon;
  176. _FlipFaces = determinant3x3 < 0;
  177. }
  178. public V3 TransformPosition(V3 position, V3[] morphTargets, in SparseWeight8 skinWeights)
  179. {
  180. position = MorphVectors(position, morphTargets);
  181. return V3.Transform(position, _WorldMatrix);
  182. }
  183. public V3 TransformNormal(V3 normal, V3[] morphTargets, in SparseWeight8 skinWeights)
  184. {
  185. normal = MorphVectors(normal, morphTargets);
  186. return V3.Normalize(V3.Transform(normal, _WorldMatrix));
  187. }
  188. public V4 TransformTangent(V4 tangent, V3[] morphTargets, in SparseWeight8 skinWeights)
  189. {
  190. var n = MorphVectors(new V3(tangent.X, tangent.Y, tangent.Z), morphTargets);
  191. n = V3.Normalize(V3.Transform(n, _WorldMatrix));
  192. return new V4(n, tangent.W);
  193. }
  194. #endregion
  195. }
  196. public class SkinTransform : MorphTransform, IGeometryTransform
  197. {
  198. #region constructor
  199. public SkinTransform() { }
  200. public SkinTransform(TRANSFORM[] invBindMatrix, TRANSFORM[] currWorldMatrix, SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
  201. {
  202. Update(morphWeights, useAbsoluteMorphTargets);
  203. Update(invBindMatrix, currWorldMatrix);
  204. }
  205. public SkinTransform(int count, Func<int, TRANSFORM> invBindMatrix, Func<int, TRANSFORM> currWorldMatrix, SparseWeight8 morphWeights, bool useAbsoluteMorphTargets)
  206. {
  207. Update(morphWeights, useAbsoluteMorphTargets);
  208. Update(count, invBindMatrix, currWorldMatrix);
  209. }
  210. #endregion
  211. #region data
  212. private TRANSFORM[] _SkinTransforms;
  213. #endregion
  214. #region properties
  215. /// <summary>
  216. /// Gets the collection of the current, final matrices to use for skinning
  217. /// </summary>
  218. public IReadOnlyList<TRANSFORM> SkinMatrices => _SkinTransforms;
  219. #endregion
  220. #region API
  221. public void Update(TRANSFORM[] invBindMatrix, TRANSFORM[] currWorldMatrix)
  222. {
  223. Guard.NotNull(invBindMatrix, nameof(invBindMatrix));
  224. Guard.NotNull(currWorldMatrix, nameof(currWorldMatrix));
  225. Guard.IsTrue(invBindMatrix.Length == currWorldMatrix.Length, nameof(currWorldMatrix), $"{invBindMatrix} and {currWorldMatrix} length mismatch.");
  226. if (_SkinTransforms == null || _SkinTransforms.Length != invBindMatrix.Length) _SkinTransforms = new TRANSFORM[invBindMatrix.Length];
  227. for (int i = 0; i < _SkinTransforms.Length; ++i)
  228. {
  229. _SkinTransforms[i] = invBindMatrix[i] * currWorldMatrix[i];
  230. }
  231. }
  232. public void Update(int count, Func<int, TRANSFORM> invBindMatrix, Func<int, TRANSFORM> currWorldMatrix)
  233. {
  234. Guard.NotNull(invBindMatrix, nameof(invBindMatrix));
  235. Guard.NotNull(currWorldMatrix, nameof(currWorldMatrix));
  236. if (_SkinTransforms == null || _SkinTransforms.Length != count) _SkinTransforms = new TRANSFORM[count];
  237. for (int i = 0; i < _SkinTransforms.Length; ++i)
  238. {
  239. _SkinTransforms[i] = invBindMatrix(i) * currWorldMatrix(i);
  240. }
  241. }
  242. public bool Visible => true;
  243. public bool FlipFaces => false;
  244. public V3 TransformPosition(V3 localPosition, V3[] morphTargets, in SparseWeight8 skinWeights)
  245. {
  246. Guard.NotNull(skinWeights, nameof(skinWeights));
  247. localPosition = MorphVectors(localPosition, morphTargets);
  248. var worldPosition = V3.Zero;
  249. var wnrm = 1.0f / skinWeights.WeightSum;
  250. foreach (var jw in skinWeights.GetIndexedWeights())
  251. {
  252. worldPosition += V3.Transform(localPosition, _SkinTransforms[jw.Item1]) * jw.Item2 * wnrm;
  253. }
  254. return worldPosition;
  255. }
  256. public V3 TransformNormal(V3 localNormal, V3[] morphTargets, in SparseWeight8 skinWeights)
  257. {
  258. Guard.NotNull(skinWeights, nameof(skinWeights));
  259. localNormal = MorphVectors(localNormal, morphTargets);
  260. var worldNormal = V3.Zero;
  261. foreach (var jw in skinWeights.GetIndexedWeights())
  262. {
  263. worldNormal += V3.TransformNormal(localNormal, _SkinTransforms[jw.Item1]) * jw.Item2;
  264. }
  265. return V3.Normalize(localNormal);
  266. }
  267. public V4 TransformTangent(V4 localTangent, V3[] morphTargets, in SparseWeight8 skinWeights)
  268. {
  269. Guard.NotNull(skinWeights, nameof(skinWeights));
  270. var localTangentV = MorphVectors(new V3(localTangent.X, localTangent.Y, localTangent.Z), morphTargets);
  271. var worldTangent = V3.Zero;
  272. foreach (var jw in skinWeights.GetIndexedWeights())
  273. {
  274. worldTangent += V3.TransformNormal(localTangentV, _SkinTransforms[jw.Item1]) * jw.Item2;
  275. }
  276. worldTangent = V3.Normalize(worldTangent);
  277. return new V4(worldTangent, localTangent.W);
  278. }
  279. #endregion
  280. #region helper utilities
  281. /// <summary>
  282. /// Calculates the inverse bind matrix to use for runtime skinning.
  283. /// </summary>
  284. /// <param name="meshWorldTransform">The world space <see cref="TRANSFORM"/> of the mesh at the time of binding (POSE).</param>
  285. /// <param name="jointWorldTransform">The world space <see cref="TRANSFORM"/> of the given bone joint at the time of binding (POSE).</param>
  286. /// <returns>A <see cref="TRANSFORM"/> representing the inverse bind transform.</returns>
  287. public static TRANSFORM CalculateInverseBinding(TRANSFORM meshWorldTransform, TRANSFORM jointWorldTransform)
  288. {
  289. // var xform = meshWorldTransform.Inverse();
  290. // xform = jointWorldTransform * xform;
  291. // return xform.Inverse();
  292. var invJoint = jointWorldTransform.Inverse();
  293. return meshWorldTransform * invJoint;
  294. }
  295. #endregion
  296. }
  297. }