MeshBuilderTests.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Numerics;
  5. using NUnit.Framework;
  6. using SharpGLTF.Geometry.VertexTypes;
  7. using SharpGLTF.Geometry.Parametric;
  8. using SharpGLTF.Schema2;
  9. using SharpGLTF.Scenes;
  10. namespace SharpGLTF.Geometry
  11. {
  12. using VERTEX1 = VertexBuilder<VertexPosition, VertexColor1Texture1, VertexJoints4>;
  13. using VERTEX2 = VertexBuilder<VertexPositionNormal, VertexColor1Texture1, VertexJoints4>;
  14. [Category("Toolkit.Geometry")]
  15. public class MeshBuilderTests
  16. {
  17. [Test]
  18. public void CreateInvalidTriangles()
  19. {
  20. var m = new Materials.MaterialBuilder();
  21. var mb = VERTEX2.CreateCompatibleMesh();
  22. // replaces default preprocessor with a debug preprocessor that throws exceptions at the slightest issue.
  23. mb.VertexPreprocessor.SetValidationPreprocessors();
  24. int TriangleCounter() { return mb.Primitives.Sum(item => item.Triangles.Count); }
  25. var prim = mb.UsePrimitive(m);
  26. var a = VERTEX2
  27. .Create(Vector3.Zero,Vector3.UnitX)
  28. .WithMaterial(Vector4.One,Vector2.Zero)
  29. .WithSkinning((0,1));
  30. var b = VERTEX2
  31. .Create(Vector3.UnitX, Vector3.UnitX)
  32. .WithMaterial(Vector4.One, Vector2.Zero)
  33. .WithSkinning((0, 1));
  34. var c = VERTEX2
  35. .Create(Vector3.UnitY, Vector3.UnitX)
  36. .WithMaterial(Vector4.One, Vector2.Zero)
  37. .WithSkinning((0, 1));
  38. prim.AddTriangle(a, b, c);
  39. Assert.That(TriangleCounter(), Is.EqualTo(1));
  40. var v2nan = new Vector2(float.NaN, float.NaN);
  41. var v3nan = new Vector3(float.NaN, float.NaN, float.NaN);
  42. var v4nan = new Vector4(float.NaN, float.NaN, float.NaN, float.NaN);
  43. Assert.Throws(typeof(ArgumentException), () => prim.AddTriangle(a.WithGeometry(v3nan), b, c));
  44. Assert.That(TriangleCounter(), Is.EqualTo(1));
  45. Assert.Throws(typeof(ArgumentException), () => prim.AddTriangle(a.WithGeometry(Vector3.Zero, v3nan), b, c));
  46. Assert.That(TriangleCounter(), Is.EqualTo(1));
  47. Assert.Throws(typeof(ArgumentOutOfRangeException), () => prim.AddTriangle(a.WithGeometry(Vector3.Zero, Vector3.Zero), b, c));
  48. Assert.That(TriangleCounter(), Is.EqualTo(1));
  49. Assert.Throws(typeof(ArgumentOutOfRangeException), () => prim.AddTriangle(a.WithGeometry(Vector3.Zero, Vector3.UnitX * 0.8f), b, c));
  50. Assert.That(TriangleCounter(), Is.EqualTo(1));
  51. Assert.Throws(typeof(ArgumentException), () => prim.AddTriangle(a.WithMaterial(v2nan), b, c));
  52. Assert.That(TriangleCounter(), Is.EqualTo(1));
  53. Assert.Throws(typeof(ArgumentException), () => prim.AddTriangle(a.WithMaterial(v4nan), b, c));
  54. Assert.That(TriangleCounter(), Is.EqualTo(1));
  55. Assert.Throws(typeof(ArgumentOutOfRangeException), () => prim.AddTriangle(a.WithMaterial(Vector4.One*2), b, c));
  56. Assert.That(TriangleCounter(), Is.EqualTo(1));
  57. Assert.Throws(typeof(ArgumentOutOfRangeException), () => prim.AddTriangle(a.WithMaterial(-Vector4.One), b, c));
  58. Assert.That(TriangleCounter(), Is.EqualTo(1));
  59. Assert.Throws(typeof(ArgumentOutOfRangeException), () => prim.AddTriangle(a.WithSkinning((0,0)), b, c));
  60. Assert.That(TriangleCounter(), Is.EqualTo(1));
  61. }
  62. [Test]
  63. public void CreateMeshInSanitizedMode()
  64. {
  65. var mesh = VERTEX2.CreateCompatibleMesh();
  66. mesh.VertexPreprocessor.SetSanitizerPreprocessors();
  67. var prim = mesh.UsePrimitive(Materials.MaterialBuilder.CreateDefault(), 1);
  68. var p = new VertexPositionNormal(Vector3.UnitX, new Vector3(float.NaN));
  69. var m = new VertexColor1Texture1(Vector4.One * 7, new Vector2(float.NaN));
  70. var s = new VertexJoints4((0, 2), (1, 7), (2, 6), (3, 5));
  71. var v1Idx = prim.AddPoint(new VERTEX2(p, m, s));
  72. var v1Bis = prim.Vertices[v1Idx];
  73. NumericsAssert.AreEqual(v1Bis.Geometry.Position, Vector3.UnitX);
  74. NumericsAssert.AreEqual(v1Bis.Geometry.Normal, Vector3.UnitX);
  75. NumericsAssert.AreEqual(v1Bis.Material.Color, Vector4.One);
  76. NumericsAssert.AreEqual(v1Bis.Material.TexCoord, Vector2.Zero);
  77. NumericsAssert.AreEqual(v1Bis.Skinning.Joints, new Vector4(1, 2, 3, 0));
  78. NumericsAssert.AreEqual(v1Bis.Skinning.Weights, new Vector4(7, 6, 5, 2) / (7f + 6f + 5f + 2f));
  79. }
  80. [Test]
  81. public static void CreateWithDegeneratedTriangle()
  82. {
  83. // create materials
  84. var material1 = new Materials.MaterialBuilder()
  85. .WithMetallicRoughnessShader()
  86. .WithChannelParam(Materials.KnownChannel.BaseColor, Materials.KnownProperty.RGBA, Vector4.One * 0.5f);
  87. var material2 = new Materials.MaterialBuilder()
  88. .WithMetallicRoughnessShader()
  89. .WithChannelParam(Materials.KnownChannel.BaseColor, Materials.KnownProperty.RGBA, Vector4.One * 0.7f);
  90. // create a mesh with degenerated triangles
  91. var validTriangle =
  92. (
  93. new Vector3(4373.192624189425f, 5522.678275192156f, -359.8238015332605f),
  94. new Vector3(4370.978060142137f, 5522.723320999183f, -359.89184701762827f),
  95. new Vector3(4364.615741107147f, 5511.510615546256f, -359.08922455413233f)
  96. );
  97. var degeneratedTriangle =
  98. (
  99. new Vector3(4374.713581837248f, 5519.741978117265f, -360.87014389818034f),
  100. new Vector3(4373.187151107471f, 5521.493282925338f, -355.70835120644153f),
  101. new Vector3(4373.187151107471f, 5521.493282925338f, -355.70835120644153f)
  102. );
  103. var mesh = new MeshBuilder<VertexPosition>("mesh");
  104. mesh.VertexPreprocessor.SetValidationPreprocessors();
  105. var validIndices = mesh.UsePrimitive(material1)
  106. .AddTriangle
  107. (
  108. new VertexPosition(validTriangle.Item1),
  109. new VertexPosition(validTriangle.Item2),
  110. new VertexPosition(validTriangle.Item3)
  111. );
  112. Assert.That(validIndices.A, Is.GreaterThanOrEqualTo(0));
  113. Assert.That(validIndices.B, Is.GreaterThanOrEqualTo(0));
  114. Assert.That(validIndices.C, Is.GreaterThanOrEqualTo(0));
  115. var degenIndices = mesh.UsePrimitive(material2)
  116. .AddTriangle
  117. (
  118. new VertexPosition(degeneratedTriangle.Item1),
  119. new VertexPosition(degeneratedTriangle.Item2),
  120. new VertexPosition(degeneratedTriangle.Item3)
  121. );
  122. Assert.That(degenIndices.A, Is.LessThan(0));
  123. Assert.That(degenIndices.B, Is.LessThan(0));
  124. Assert.That(degenIndices.C, Is.LessThan(0));
  125. // create scene:
  126. var scene = new SceneBuilder();
  127. scene.AddRigidMesh(mesh, Matrix4x4.Identity);
  128. // check gltf2
  129. var model = scene.ToGltf2();
  130. Assert.That(model.LogicalMeshes[0].Primitives, Has.Count.EqualTo(1));
  131. }
  132. [Test]
  133. public static void CreateWithMutableSharedMaterial()
  134. {
  135. // create materials
  136. var material1 = Materials.MaterialBuilder.CreateDefault();
  137. var material2 = Materials.MaterialBuilder.CreateDefault();
  138. var material3 = Materials.MaterialBuilder.CreateDefault();
  139. Assert.That(Materials.MaterialBuilder.AreEqualByContent(material1, material2), Is.True);
  140. Assert.That(Materials.MaterialBuilder.AreEqualByContent(material1, material3), Is.True);
  141. Assert.That(material2 != material1);
  142. Assert.That(material3 != material1);
  143. // MeshBuilder should split primitives by material reference,
  144. // because in general, materials will not be immutable.
  145. var mesh = new MeshBuilder<VertexPosition>();
  146. mesh.UsePrimitive(material1, 1).AddPoint(default);
  147. mesh.UsePrimitive(material2, 1).AddPoint(default);
  148. mesh.UsePrimitive(material3, 1).AddPoint(default);
  149. Assert.That(mesh.Primitives, Has.Count.EqualTo(3));
  150. // create scene
  151. var scene = new SceneBuilder();
  152. scene.AddRigidMesh(mesh, Matrix4x4.Identity);
  153. // check gltf
  154. // The build process should identify that, at this point, material1, material2 and material3
  155. // represent the same material, and coalesce to a single material.
  156. var gltfModel = scene.ToGltf2();
  157. Assert.That(gltfModel.LogicalMaterials, Has.Count.EqualTo(1));
  158. // since Materials.MaterialBuilder is not immutable we can change the contents,
  159. // so now, material1, material2 and material3 no longer represent the same material
  160. material1.WithMetallicRoughnessShader().WithChannelParam(Materials.KnownChannel.BaseColor, Materials.KnownProperty.RGBA, Vector4.One * 0.2f);
  161. material2.WithMetallicRoughnessShader().WithChannelParam(Materials.KnownChannel.BaseColor, Materials.KnownProperty.RGBA, Vector4.One * 0.4f);
  162. material3.WithMetallicRoughnessShader().WithChannelParam(Materials.KnownChannel.BaseColor, Materials.KnownProperty.RGBA, Vector4.One * 0.6f);
  163. gltfModel = scene.ToGltf2();
  164. Assert.That(gltfModel.LogicalMaterials, Has.Count.EqualTo(3));
  165. }
  166. [Test]
  167. public void CreateMeshWithTriangleAndQuad()
  168. {
  169. var dmat = Materials.MaterialBuilder.CreateDefault();
  170. var mesh = VERTEX1.CreateCompatibleMesh();
  171. var prim = mesh.UsePrimitive(dmat);
  172. var triIdx = prim.AddTriangle(new VERTEX1(Vector3.Zero), new VERTEX1(Vector3.UnitX * 2) , new VERTEX1(Vector3.UnitY * 2));
  173. var qadIdx = prim.AddQuadrangle(new VERTEX1(-Vector3.UnitX), new VERTEX1(Vector3.UnitY), new VERTEX1(Vector3.UnitX), new VERTEX1(-Vector3.UnitY));
  174. Assert.That(prim.Vertices, Has.Count.EqualTo(7));
  175. Assert.That(prim.Triangles, Has.Count.EqualTo(3));
  176. Assert.That(prim.Surfaces, Has.Count.EqualTo(2));
  177. Assert.That(triIdx, Is.EqualTo((0, 1, 2)));
  178. Assert.That(qadIdx, Is.EqualTo((3, 4, 5, 6)));
  179. Assert.That(prim.GetIndices(), Is.EqualTo(new[] { 0, 1, 2, 3, 4, 5, 3, 5, 6 }));
  180. }
  181. [Test]
  182. public void CreateMeshWithCustomVertexAttribute()
  183. {
  184. var dmat = Materials.MaterialBuilder.CreateDefault();
  185. var mesh = new MeshBuilder<VertexPosition, VertexColor1Texture1Custom1, VertexEmpty>();
  186. var prim = mesh.UsePrimitive(dmat);
  187. prim.AddTriangle
  188. (
  189. (Vector3.UnitX, (Vector4.One, Vector2.Zero, 0.1f)),
  190. (Vector3.UnitY, (Vector4.One, Vector2.Zero, 0.2f)),
  191. (Vector3.UnitZ, (Vector4.One, Vector2.Zero, 0.3f))
  192. );
  193. var dstScene = new Schema2.ModelRoot();
  194. var dstMesh = dstScene.CreateMesh(mesh);
  195. var batchId = dstMesh.Primitives[0].GetVertexAccessor(VertexColor1Texture1Custom1.CUSTOMATTRIBUTENAME).AsScalarArray();
  196. Assert.That(batchId, Is.EqualTo(new float[] { 0.1f, 0.2f, 0.3f }));
  197. }
  198. [Test]
  199. public void GenerateTangents()
  200. {
  201. var vertices = new VertexBufferColumns();
  202. vertices.Positions = new[] { Vector3.Zero, Vector3.UnitX, Vector3.UnitY };
  203. vertices.Normals = new[] { Vector3.UnitZ, Vector3.UnitZ, Vector3.UnitZ };
  204. vertices.TexCoords0 = new[] { Vector2.Zero, Vector2.UnitX, Vector2.UnitY };
  205. var indices = new[] { (0,1,2) } as IEnumerable<(int,int,int)>;
  206. VertexBufferColumns.CalculateTangents(new[] { (vertices, indices) });
  207. }
  208. [Test]
  209. public void CloneMeshBuilder()
  210. {
  211. var material1 = Materials.MaterialBuilder.CreateDefault();
  212. var material2 = Materials.MaterialBuilder.CreateDefault();
  213. var mesh = new MeshBuilder<VertexPosition>();
  214. mesh.AddCube(material1, Matrix4x4.Identity);
  215. mesh.AddSphere(material2, 5, Matrix4x4.CreateTranslation(0, 10, 0));
  216. var cloned1 = mesh.Clone( m => m.Clone() );
  217. var primitivePairs = mesh.Primitives.Zip(cloned1.Primitives, (src, dst) => (src, dst));
  218. foreach(var (src, dst) in primitivePairs)
  219. {
  220. Assert.That(dst.Material, Is.Not.SameAs(src.Material));
  221. Assert.That(dst.Triangles, Has.Count.EqualTo(src.Triangles.Count));
  222. Assert.That(dst.Triangles, Is.EqualTo(src.Triangles));
  223. }
  224. var material3 = Materials.MaterialBuilder.CreateDefault();
  225. // force all geometries to use a single material,
  226. // which should result in a mesh with with all the primitives
  227. // of the source mesh merged into a single primitive.
  228. var cloned2 = cloned1.Clone(m => material3);
  229. Assert.That(cloned2.Primitives.Count, Is.EqualTo(1));
  230. Assert.That(cloned2.Primitives.First().Material, Is.SameAs(material3));
  231. Assert.That(cloned2.Primitives.Sum(item => item.Triangles.Count), Is.EqualTo(cloned1.Primitives.Sum(item => item.Triangles.Count)));
  232. }
  233. }
  234. }