MeshDecoder.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using XY = System.Numerics.Vector2;
  6. using XYZ = System.Numerics.Vector3;
  7. using XYZW = System.Numerics.Vector4;
  8. namespace SharpGLTF.Runtime
  9. {
  10. /// <summary>
  11. /// Exposes an API to allow decoding a geometry mesh.
  12. /// </summary>
  13. /// <typeparam name="TMaterial">The primitive's material type</typeparam>
  14. /// <remarks>
  15. /// Implemented by <see cref="_MeshDecoder{TMaterial}"/>
  16. /// </remarks>
  17. public interface IMeshDecoder<TMaterial>
  18. where TMaterial : class
  19. {
  20. string Name { get; }
  21. Object Extras { get; }
  22. int LogicalIndex { get; }
  23. IReadOnlyList<IMeshPrimitiveDecoder<TMaterial>> Primitives { get; }
  24. }
  25. /// <summary>
  26. /// Exposes an API to get geometry data from a mesh primitive
  27. /// </summary>
  28. /// <remarks>
  29. /// Implemented by <see cref="_MeshPrimitiveDecoder"/>
  30. /// </remarks>
  31. public interface IMeshPrimitiveDecoder
  32. {
  33. #region properties
  34. /// <summary>
  35. /// Gets a value indicating the total number of vertices for this primitive.
  36. /// </summary>
  37. int VertexCount { get; }
  38. /// <summary>
  39. /// Gets a value indicating the total number of morph targets for this primitive.
  40. /// </summary>
  41. int MorphTargetsCount { get; }
  42. /// <summary>
  43. /// Gets a value indicating the number of color vertex attributes.
  44. /// In the range of 0 to 2.
  45. /// </summary>
  46. int ColorsCount { get; }
  47. /// <summary>
  48. /// Gets a value indicating the number of texture coordinate vertex attributes.
  49. /// In the range of 0 to 2.
  50. /// </summary>
  51. int TexCoordsCount { get; }
  52. /// <summary>
  53. /// Gets a value indicating the number of skinning joint-weight attributes.
  54. /// The values can be 0, 4 or 8.
  55. /// </summary>
  56. int JointsWeightsCount { get; }
  57. /// <summary>
  58. /// Gets a sequence of tuples where each item represents the vertex indices of a line.
  59. /// </summary>
  60. IEnumerable<(int A, int B)> LineIndices { get; }
  61. /// <summary>
  62. /// Gets a sequence of tuples where each item represents the vertex indices of a triangle.
  63. /// </summary>
  64. IEnumerable<(int A, int B, int C)> TriangleIndices { get; }
  65. #endregion
  66. #region API
  67. XYZ GetPosition(int vertexIndex);
  68. XYZ GetNormal(int vertexIndex);
  69. XYZW GetTangent(int vertexIndex);
  70. XY GetTextureCoord(int vertexIndex, int textureSetIndex);
  71. XYZW GetColor(int vertexIndex, int colorSetIndex);
  72. Transforms.SparseWeight8 GetSkinWeights(int vertexIndex);
  73. IReadOnlyList<XYZ> GetPositionDeltas(int vertexIndex);
  74. IReadOnlyList<XYZ> GetNormalDeltas(int vertexIndex);
  75. IReadOnlyList<XYZ> GetTangentDeltas(int vertexIndex);
  76. IReadOnlyList<XY> GetTextureCoordDeltas(int vertexIndex, int textureSetIndex);
  77. IReadOnlyList<XYZW> GetColorDeltas(int vertexIndex, int colorSetIndex);
  78. #endregion
  79. }
  80. /// <summary>
  81. /// Exposes an API to get geometry data from a mesh primitive
  82. /// </summary>
  83. /// <typeparam name="TMaterial">The material type used by the primitive</typeparam>
  84. /// <remarks>
  85. /// Implemented by <see cref="_MeshPrimitiveDecoder{TMaterial}"/>
  86. /// </remarks>
  87. public interface IMeshPrimitiveDecoder<TMaterial> : IMeshPrimitiveDecoder
  88. where TMaterial : class
  89. {
  90. TMaterial Material { get; }
  91. }
  92. /// <summary>
  93. /// Utility methods to help decode Meshes.
  94. /// </summary>
  95. public static class MeshDecoder
  96. {
  97. public static IMeshDecoder<Schema2.Material> Decode(this Schema2.Mesh mesh, RuntimeOptions options = null)
  98. {
  99. if (mesh == null) return null;
  100. var meshDecoder = new _MeshDecoder<Schema2.Material>(mesh, options);
  101. meshDecoder.GenerateNormalsAndTangents();
  102. return meshDecoder;
  103. }
  104. public static IMeshDecoder<Schema2.Material>[] Decode(this IReadOnlyList<Schema2.Mesh> meshes, RuntimeOptions options = null)
  105. {
  106. Guard.NotNull(meshes, nameof(meshes));
  107. return meshes.Select(item => item.Decode(options)).ToArray();
  108. }
  109. public static XYZ GetPosition(this IMeshPrimitiveDecoder primitive, int idx, Transforms.IGeometryTransform xform)
  110. {
  111. Guard.NotNull(primitive, nameof(primitive));
  112. Guard.MustBeBetweenOrEqualTo(idx, 0, primitive.VertexCount + 1, nameof(idx));
  113. Guard.NotNull(xform, nameof(xform));
  114. var p = primitive.GetPosition(idx);
  115. var d = primitive.GetPositionDeltas(idx);
  116. var w = primitive.GetSkinWeights(idx);
  117. return xform.TransformPosition(p, d, w);
  118. }
  119. public static XYZ GetNormal(this IMeshPrimitiveDecoder primitive, int idx, Transforms.IGeometryTransform xform)
  120. {
  121. Guard.NotNull(primitive, nameof(primitive));
  122. Guard.MustBeBetweenOrEqualTo(idx, 0, primitive.VertexCount + 1, nameof(idx));
  123. Guard.NotNull(xform, nameof(xform));
  124. var n = primitive.GetNormal(idx);
  125. var d = primitive.GetNormalDeltas(idx);
  126. var w = primitive.GetSkinWeights(idx);
  127. return xform.TransformNormal(n, d, w);
  128. }
  129. public static XYZW GetTangent(this IMeshPrimitiveDecoder primitive, int idx, Transforms.IGeometryTransform xform)
  130. {
  131. Guard.NotNull(primitive, nameof(primitive));
  132. Guard.MustBeBetweenOrEqualTo(idx, 0, primitive.VertexCount + 1, nameof(idx));
  133. Guard.NotNull(xform, nameof(xform));
  134. var t = primitive.GetTangent(idx);
  135. var d = primitive.GetTangentDeltas(idx);
  136. var w = primitive.GetSkinWeights(idx);
  137. return xform.TransformTangent(t, d, w);
  138. }
  139. public static (XYZ Min, XYZ Max) EvaluateBoundingBox(this Schema2.Scene scene, float samplingTimeStep = 1.0f)
  140. {
  141. Guard.NotNull(scene, nameof(scene));
  142. var decodedMeshes = scene.LogicalParent.LogicalMeshes.Decode();
  143. var sceneTemplate = SceneTemplate.Create(scene);
  144. var sceneInstance = sceneTemplate.CreateInstance();
  145. var armatureInst = sceneInstance.Armature;
  146. if (armatureInst.AnimationTracks.Count == 0)
  147. {
  148. armatureInst.SetPoseTransforms();
  149. return sceneInstance.EvaluateBoundingBox(decodedMeshes);
  150. }
  151. var min = new XYZ(float.PositiveInfinity);
  152. var max = new XYZ(float.NegativeInfinity);
  153. for (int trackIdx = 0; trackIdx < armatureInst.AnimationTracks.Count; ++trackIdx)
  154. {
  155. var duration = armatureInst.AnimationTracks[trackIdx].Duration;
  156. for (float time = 0; time < duration; time += samplingTimeStep)
  157. {
  158. armatureInst.SetAnimationFrame(trackIdx, time);
  159. var (fMin, fMax) = sceneInstance.EvaluateBoundingBox(decodedMeshes);
  160. min = XYZ.Min(min, fMin);
  161. max = XYZ.Max(max, fMax);
  162. }
  163. }
  164. return (min, max);
  165. }
  166. public static (XYZ Center, Single Radius) EvaluateBoundingSphere(this Schema2.Scene scene, float samplingTimeStep = 1.0f)
  167. {
  168. Guard.NotNull(scene, nameof(scene));
  169. var decodedMeshes = scene.LogicalParent.LogicalMeshes.Decode();
  170. var sceneTemplate = SceneTemplate.Create(scene);
  171. var sceneInstance = sceneTemplate.CreateInstance();
  172. var armatureInst = sceneInstance.Armature;
  173. if (armatureInst.AnimationTracks.Count == 0)
  174. {
  175. armatureInst.SetPoseTransforms();
  176. return sceneInstance.EvaluateBoundingSphere(decodedMeshes);
  177. }
  178. var center = XYZ.Zero;
  179. float radius = -1f;
  180. for (int trackIdx = 0; trackIdx < armatureInst.AnimationTracks.Count; ++trackIdx)
  181. {
  182. var duration = armatureInst.AnimationTracks[trackIdx].Duration;
  183. for (float time = 0; time < duration; time += samplingTimeStep)
  184. {
  185. armatureInst.SetAnimationFrame(trackIdx, time);
  186. var (fc, fr) = sceneInstance.EvaluateBoundingSphere(decodedMeshes);
  187. _MergeSphere(ref center, ref radius, fc, fr);
  188. }
  189. }
  190. return (center, radius);
  191. }
  192. public static (XYZ Min, XYZ Max) EvaluateBoundingBox<TMaterial>(this SceneInstance instance, IReadOnlyList<IMeshDecoder<TMaterial>> meshes)
  193. where TMaterial : class
  194. {
  195. Guard.NotNull(instance, nameof(instance));
  196. Guard.NotNull(meshes, nameof(meshes));
  197. var min = new XYZ(float.PositiveInfinity);
  198. var max = new XYZ(float.NegativeInfinity);
  199. foreach (var pos in instance.GetWorldVertices(meshes))
  200. {
  201. min = XYZ.Min(min, pos);
  202. max = XYZ.Max(max, pos);
  203. }
  204. return (min, max);
  205. }
  206. public static (XYZ Center, Single Radius) EvaluateBoundingSphere<TMaterial>(this SceneInstance instance, IReadOnlyList<IMeshDecoder<TMaterial>> meshes)
  207. where TMaterial : class
  208. {
  209. Guard.NotNull(instance, nameof(instance));
  210. Guard.NotNull(meshes, nameof(meshes));
  211. var center = XYZ.Zero;
  212. var radius = -1f;
  213. foreach (var p1 in instance.GetWorldVertices(meshes))
  214. {
  215. _AddPointToSphere(ref center, ref radius, p1);
  216. }
  217. return (center, radius);
  218. }
  219. private static void _AddPointToSphere(ref XYZ c1, ref float r1, XYZ c2)
  220. {
  221. if (r1 < 0) { c1 = c2; r1 = 0; return; }
  222. var dir = c2 - c1;
  223. var len = dir.Length();
  224. if (len <= r1) return; // if inside, exit.
  225. dir /= len;
  226. var p1 = c1 - (dir * r1);
  227. c1 = (p1 + c2) / 2;
  228. r1 = (p1 - c2).Length() / 2;
  229. #if DEBUG
  230. var dist = (c2 - c1).Length();
  231. System.Diagnostics.Debug.Assert(dist <= (r1 + 0.001f));
  232. dist = (p1 - c1).Length();
  233. System.Diagnostics.Debug.Assert(dist <= (r1 + 0.001f));
  234. #endif
  235. }
  236. private static void _MergeSphere(ref XYZ c1, ref float r1, XYZ c2, float r2)
  237. {
  238. if (r1 < 0) { c1 = c2; r1 = r2; return; }
  239. var dir = c2 - c1;
  240. var len = dir.Length();
  241. if (r1 >= (r2 + len)) return; // new inside current, exit.
  242. if (r2 >= (r1 + len)) { c1 = c2; r1 = r2; return; } // current inside new, update & exit.
  243. // combine
  244. dir /= len;
  245. var p1 = c1 - (dir * r1);
  246. var p2 = c2 + (dir * r2);
  247. c1 = (p1 + p2) / 2;
  248. r1 = (p1 - p2).Length() / 2;
  249. #if DEBUG
  250. var dist = (c2 - c1).Length() + r2;
  251. System.Diagnostics.Debug.Assert(dist <= (r1 + 0.001f));
  252. #endif
  253. }
  254. public static IEnumerable<XYZ> GetWorldVertices<TMaterial>(this SceneInstance instance, IReadOnlyList<IMeshDecoder<TMaterial>> meshes)
  255. where TMaterial : class
  256. {
  257. Guard.NotNull(instance, nameof(instance));
  258. Guard.NotNull(meshes, nameof(meshes));
  259. for (int i = 0; i < meshes.Count; ++i)
  260. {
  261. Guard.MustBeEqualTo(meshes[i].LogicalIndex, i, nameof(meshes) + $"[{i}]");
  262. }
  263. return instance
  264. .Where(item => item.Transform.Visible)
  265. .SelectMany(item => meshes[item.Template.LogicalMeshIndex].GetWorldVertices(item.Transform));
  266. }
  267. public static IEnumerable<XYZ> GetWorldVertices<TMaterial>(this IMeshDecoder<TMaterial> mesh, Transforms.IGeometryTransform xform)
  268. where TMaterial : class
  269. {
  270. Guard.NotNull(mesh, nameof(mesh));
  271. Guard.NotNull(xform, nameof(xform));
  272. foreach (var childXform in Transforms.InstancingTransform.Evaluate(xform))
  273. {
  274. foreach (var primitive in mesh.Primitives)
  275. {
  276. for (int i = 0; i < primitive.VertexCount; ++i)
  277. {
  278. yield return primitive.GetPosition(i, childXform);
  279. }
  280. }
  281. }
  282. }
  283. }
  284. }