LoadSampleTests.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using NUnit.Framework;
  6. namespace SharpGLTF.Schema2.LoadAndSave
  7. {
  8. /// <summary>
  9. /// Test cases for models found in <see href="https://github.com/KhronosGroup/glTF-Sample-Models"/> and more....
  10. /// </summary>
  11. [TestFixture]
  12. [AttachmentPathFormat("*/TestResults/LoadAndSave/?", true)]
  13. [Category("Model Load and Save")]
  14. public class LoadSampleTests
  15. {
  16. #region setup
  17. [OneTimeSetUp]
  18. public void Setup()
  19. {
  20. // TestFiles.DownloadReferenceModels();
  21. }
  22. #endregion
  23. #region helpers
  24. private static ModelRoot _LoadModel(string f, bool tryFix = false)
  25. {
  26. var perf = System.Diagnostics.Stopwatch.StartNew();
  27. ModelRoot model = null;
  28. var settings = tryFix ? Validation.ValidationMode.TryFix : Validation.ValidationMode.Strict;
  29. try
  30. {
  31. model = ModelRoot.Load(f, settings);
  32. Assert.That(model, Is.Not.Null);
  33. }
  34. catch (Exception ex)
  35. {
  36. TestContext.Progress.WriteLine($"Failed {f.ToShortDisplayPath()}");
  37. Assert.Fail(ex.Message);
  38. }
  39. var perf_load = perf.ElapsedMilliseconds;
  40. // do a model clone and compare it
  41. _AssertAreEqual(model, model.DeepClone());
  42. var perf_clone = perf.ElapsedMilliseconds;
  43. if (!f.Contains("Iridescence")) // the iridescence sample models declares using IOR but it's not actually used
  44. {
  45. var unsupportedExtensions = new[] { "MSFT_lod", "EXT_lights_image_based" };
  46. // check extensions used
  47. if (unsupportedExtensions.All(uex => !model.ExtensionsUsed.Contains(uex)))
  48. {
  49. var detectedExtensions = model.GatherUsedExtensions().ToArray();
  50. Assert.That(detectedExtensions, Is.EquivalentTo(model.ExtensionsUsed));
  51. }
  52. }
  53. // Save models
  54. model.AttachToCurrentTest(System.IO.Path.ChangeExtension(System.IO.Path.GetFileName(f), ".obj"));
  55. var perf_wavefront = perf.ElapsedMilliseconds;
  56. model.AttachToCurrentTest(System.IO.Path.ChangeExtension(System.IO.Path.GetFileName(f), ".glb"));
  57. var perf_glb = perf.ElapsedMilliseconds;
  58. TestContext.Progress.WriteLine($"processed {f.ToShortDisplayPath()} - Load:{perf_load}ms Clone:{perf_clone}ms S.obj:{perf_wavefront}ms S.glb:{perf_glb}ms");
  59. return model;
  60. }
  61. private static void _AssertAreEqual(ModelRoot a, ModelRoot b)
  62. {
  63. var aa = a.GetLogicalChildrenFlattened().ToList();
  64. var bb = b.GetLogicalChildrenFlattened().ToList();
  65. Assert.That(bb, Has.Count.EqualTo(aa.Count));
  66. Assert.That
  67. (
  68. aa.Select(item => item.GetType()),
  69. Is.EqualTo(bb.Select(item => item.GetType()))
  70. );
  71. }
  72. #endregion
  73. [TestCase("\\glTF\\")]
  74. // [TestCase("\\glTF-Draco\\")] // Not supported
  75. [TestCase("\\glTF-IBL\\")]
  76. [TestCase("\\glTF-Binary\\")]
  77. [TestCase("\\glTF-Embedded\\")]
  78. // [TestCase("\\glTF-Quantized\\")] // removed from tests
  79. // [TestCase("\\glTF-pbrSpecularGlossiness\\")] // removed from tests
  80. public void LoadModelsFromKhronosSamples(string section)
  81. {
  82. TestContext.CurrentContext.AttachGltfValidatorLinks();
  83. foreach (var f in TestFiles.GetSampleModelsPaths())
  84. {
  85. if (!f.Contains(section)) continue;
  86. _LoadModel(f);
  87. }
  88. }
  89. [Test]
  90. public void LoadModelsFromBabylonJs()
  91. {
  92. TestContext.CurrentContext.AttachGltfValidatorLinks();
  93. foreach (var f in TestFiles.GetBabylonJSModelsPaths())
  94. {
  95. _LoadModel(f, true);
  96. }
  97. }
  98. [TestCase("TeapotsGalore.gltf")]
  99. [TestCase("GrassFieldInstanced.glb")]
  100. [TestCase("InstanceTest.glb")]
  101. public void LoadModelsWithGpuMeshInstancingExtension(string fileFilter)
  102. {
  103. TestContext.CurrentContext.AttachGltfValidatorLinks();
  104. var f = TestFiles.GetMeshIntancingModelPaths().FirstOrDefault(item => item.Contains(fileFilter));
  105. var model = _LoadModel(f, false);
  106. var ff = System.IO.Path.GetFileNameWithoutExtension(f);
  107. model.AttachToCurrentTest($"{ff}.loaded.glb");
  108. // perform roundtrip
  109. var roundtripDefault = model.DefaultScene
  110. .ToSceneBuilder() // glTF to SceneBuilder
  111. .ToGltf2(Scenes.SceneBuilderSchema2Settings.Default); // SceneBuilder to glTF
  112. var roundtripInstanced = model.DefaultScene
  113. .ToSceneBuilder() // glTF to SceneBuilder
  114. .ToGltf2(Scenes.SceneBuilderSchema2Settings.WithGpuInstancing); // SceneBuilder to glTF
  115. // compare bounding spheres
  116. var modelBounds = Runtime.MeshDecoder.EvaluateBoundingBox(model.DefaultScene);
  117. var rtripDefBounds = Runtime.MeshDecoder.EvaluateBoundingBox(roundtripDefault.DefaultScene);
  118. var rtripGpuBounds = Runtime.MeshDecoder.EvaluateBoundingBox(roundtripInstanced.DefaultScene);
  119. Assert.That(rtripDefBounds, Is.EqualTo(modelBounds));
  120. Assert.That(rtripGpuBounds, Is.EqualTo(modelBounds));
  121. // save results
  122. roundtripDefault.AttachToCurrentTest($"{ff}.roundtrip.default.glb");
  123. roundtripInstanced.AttachToCurrentTest($"{ff}.roundtrip.instancing.glb");
  124. }
  125. [TestCase("IridescenceMetallicSpheres.gltf")]
  126. [TestCase("SpecGlossVsMetalRough.gltf")]
  127. [TestCase(@"TextureTransformTest.gltf")]
  128. [TestCase(@"UnlitTest\glTF-Binary\UnlitTest.glb")]
  129. [TestCase(@"glTF-Quantized\Avocado.gltf")]
  130. [TestCase(@"glTF-Quantized\AnimatedMorphCube.gltf")]
  131. [TestCase(@"glTF-Quantized\AnimatedMorphCube.gltf")]
  132. [TestCase(@"glTF-Quantized\Duck.gltf")]
  133. [TestCase(@"glTF-Quantized\Lantern.gltf")]
  134. [TestCase(@"MosquitoInAmber.glb")]
  135. public void LoadModelsWithExtensions(string filePath)
  136. {
  137. TestContext.CurrentContext.AttachGltfValidatorLinks();
  138. filePath = TestFiles
  139. .GetSampleModelsPaths()
  140. .FirstOrDefault(item => item.EndsWith(filePath));
  141. _LoadModel(filePath);
  142. }
  143. [Test]
  144. public void LoadModelWithUnlitMaterial()
  145. {
  146. var f = TestFiles
  147. .GetSampleModelsPaths()
  148. .FirstOrDefault(item => item.EndsWith(@"UnlitTest\glTF-Binary\UnlitTest.glb"));
  149. var model = ModelRoot.Load(f);
  150. Assert.That(model, Is.Not.Null);
  151. Assert.That(model.LogicalMaterials[0].Unlit, Is.True);
  152. // do a model roundtrip
  153. var modelBis = ModelRoot.ParseGLB(model.WriteGLB());
  154. Assert.That(modelBis, Is.Not.Null);
  155. Assert.That(modelBis.LogicalMaterials[0].Unlit, Is.True);
  156. }
  157. [Test]
  158. public void LoadModelWithLights()
  159. {
  160. var f = TestFiles
  161. .GetSchemaExtensionsModelsPaths()
  162. .FirstOrDefault(item => item.EndsWith("lights.gltf"));
  163. var model = ModelRoot.Load(f);
  164. Assert.That(model, Is.Not.Null);
  165. Assert.That(model.LogicalPunctualLights, Has.Count.EqualTo(3));
  166. Assert.That(model.DefaultScene.VisualChildren.ElementAt(0).PunctualLight.LogicalIndex, Is.EqualTo(1));
  167. Assert.That(model.DefaultScene.VisualChildren.ElementAt(1).PunctualLight.LogicalIndex, Is.EqualTo(0));
  168. }
  169. [Test]
  170. public void LoadModelWithSparseAccessor()
  171. {
  172. var path = TestFiles
  173. .GetSampleModelsPaths()
  174. .FirstOrDefault(item => item.Contains("SimpleSparseAccessor.gltf"));
  175. var model = ModelRoot.Load(path);
  176. Assert.That(model, Is.Not.Null);
  177. var primitive = model.LogicalMeshes[0].Primitives[0];
  178. var accessor = primitive.GetVertexAccessor("POSITION");
  179. var basePositions = accessor._GetMemoryAccessor().AsVector3Array();
  180. var positions = accessor.AsVector3Array();
  181. }
  182. [Test]
  183. public void LoadModelWithMorphTargets()
  184. {
  185. var path = TestFiles
  186. .GetSampleModelsPaths()
  187. .FirstOrDefault(item => item.Contains("MorphPrimitivesTest.glb"));
  188. var model = ModelRoot.Load(path);
  189. Assert.That(model, Is.Not.Null);
  190. var triangles = model.DefaultScene
  191. .EvaluateTriangles<Geometry.VertexTypes.VertexPosition, Geometry.VertexTypes.VertexEmpty>(null, null, 0)
  192. .ToArray();
  193. model.AttachToCurrentTest(System.IO.Path.ChangeExtension(System.IO.Path.GetFileName(path), ".obj"));
  194. model.AttachToCurrentTest(System.IO.Path.ChangeExtension(System.IO.Path.GetFileName(path), ".glb"));
  195. }
  196. [TestCase("RiggedFigure.glb")]
  197. [TestCase("RiggedSimple.glb")]
  198. [TestCase("BoxAnimated.glb")]
  199. [TestCase("AnimatedMorphCube.glb")]
  200. [TestCase("AnimatedMorphSphere.glb")]
  201. [TestCase("CesiumMan.glb")]
  202. //[TestCase("Monster.glb")] // temporarily removed from khronos repo
  203. [TestCase("BrainStem.glb")]
  204. [TestCase("Fox.glb")]
  205. public void LoadModelsWithAnimations(string path)
  206. {
  207. path = TestFiles
  208. .GetSampleModelsPaths()
  209. .FirstOrDefault(item => item.Contains(path));
  210. var model = ModelRoot.Load(path);
  211. Assert.That(model, Is.Not.Null);
  212. path = System.IO.Path.GetFileNameWithoutExtension(path);
  213. model.AttachToCurrentTest(path + ".glb");
  214. var triangles = model.DefaultScene
  215. .EvaluateTriangles<Geometry.VertexTypes.VertexPosition, Geometry.VertexTypes.VertexEmpty>()
  216. .ToArray();
  217. var anim = model.LogicalAnimations[0];
  218. var duration = anim.Duration;
  219. for(int i=0; i < 10; ++i)
  220. {
  221. var t = duration * i / 10;
  222. int tt = (int)(t * 1000.0f);
  223. model.AttachToCurrentTest($"{path} at {tt}.obj",anim, t);
  224. }
  225. }
  226. [Test]
  227. public void LoadAnimatedMorphCube()
  228. {
  229. var path = TestFiles
  230. .GetSampleModelsPaths()
  231. .FirstOrDefault(item => item.Contains("AnimatedMorphCube.glb"));
  232. var model = ModelRoot.Load(path);
  233. Assert.That(model, Is.Not.Null);
  234. var anim = model.LogicalAnimations[0];
  235. var node = model.LogicalNodes[0];
  236. var acc_master = node.Mesh.Primitives[0].GetVertexAccessor("POSITION");
  237. var acc_morph0 = node.Mesh.Primitives[0].GetMorphTargetAccessors(0)["POSITION"];
  238. var acc_morph1 = node.Mesh.Primitives[0].GetMorphTargetAccessors(1)["POSITION"];
  239. var pos_master = acc_master.AsVector3Array();
  240. var pos_morph0 = acc_morph0.AsVector3Array();
  241. var pos_morph1 = acc_morph1.AsVector3Array();
  242. // pos_master
  243. var instance = Runtime.SceneTemplate
  244. .Create(model.DefaultScene)
  245. .CreateInstance();
  246. var pvrt = node.Mesh.Primitives[0].GetVertexColumns();
  247. for (float t = 0; t < 5; t+=0.25f)
  248. {
  249. instance.Armature.SetAnimationFrame(anim.LogicalIndex, t);
  250. var nodexform = instance.GetDrawableInstance(0).Transform;
  251. TestContext.WriteLine($"Animation at {t}");
  252. var curves = node.GetCurveSamplers(anim);
  253. if (t < anim.Duration)
  254. {
  255. var mw = curves.GetMorphingSampler<float[]>()
  256. .CreateCurveSampler()
  257. .GetPoint(t);
  258. TestContext.WriteLine($" Morph Weights: {mw[0]} {mw[1]}");
  259. }
  260. var msw = curves.GetMorphingSampler<Transforms.SparseWeight8>()
  261. .CreateCurveSampler()
  262. .GetPoint(t);
  263. TestContext.WriteLine($" Morph Sparse : {msw.Weight0} {msw.Weight1}");
  264. var triangles = model.DefaultScene
  265. .EvaluateTriangles<Geometry.VertexTypes.VertexPosition, Geometry.VertexTypes.VertexEmpty>(null, anim, t)
  266. .ToList();
  267. var vertices = triangles
  268. .SelectMany(item => new[] { item.A.Position, item.B.Position, item.C.Position })
  269. .Distinct()
  270. .ToList();
  271. foreach (var v in vertices) TestContext.WriteLine($"{v}");
  272. TestContext.WriteLine();
  273. }
  274. }
  275. [Test]
  276. public void LoadMultiUVTexture()
  277. {
  278. var path = TestFiles
  279. .GetSampleModelsPaths()
  280. .FirstOrDefault(item => item.Contains("TextureTransformMultiTest.glb"));
  281. var model = ModelRoot.Load(path);
  282. Assert.That(model, Is.Not.Null);
  283. var materials = model.LogicalMaterials;
  284. var normalTest0Mat = materials.FirstOrDefault(item => item.Name == "NormalTest0Mat");
  285. var normalTest0Mat_normal = normalTest0Mat.FindChannel("Normal").Value;
  286. var normalTest0Mat_normal_xform = normalTest0Mat_normal.TextureTransform;
  287. Assert.That(normalTest0Mat_normal_xform, Is.Not.Null);
  288. }
  289. [Test]
  290. public void FindDependencyFiles()
  291. {
  292. TestContext.CurrentContext.AttachGltfValidatorLinks();
  293. foreach (var f in TestFiles.GetBabylonJSModelsPaths())
  294. {
  295. TestContext.WriteLine(f);
  296. var dependencies = ModelRoot.GetSatellitePaths(f);
  297. foreach(var d in dependencies)
  298. {
  299. TestContext.WriteLine($" {d}");
  300. }
  301. TestContext.WriteLine();
  302. }
  303. }
  304. }
  305. }