TestFiles.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using NUnit.Framework;
  6. namespace SharpGLTF
  7. {
  8. /// <summary>
  9. /// Centralices the access to test files.
  10. /// </summary>
  11. public static class TestFiles
  12. {
  13. #region lifecycle
  14. private static void _EnsureInitialized()
  15. {
  16. if (_TestFilesDir != null) return;
  17. var wdir = TestContext.CurrentContext.WorkDirectory;
  18. var examplesFound = false;
  19. while (wdir.Length > 3)
  20. {
  21. _TestFilesDir = System.IO.Path.Combine(wdir, "TestFiles");
  22. if (wdir.ToLowerInvariant().EndsWith("tests") && System.IO.Directory.Exists(_TestFilesDir))
  23. {
  24. examplesFound = true;
  25. break;
  26. }
  27. wdir = System.IO.Path.GetDirectoryName(wdir);
  28. }
  29. Assert.That(examplesFound, "TestFiles directory not found; please, run '1_DownloadTestFiles.cmd' before running the tests.");
  30. _AssetFilesDir = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(_TestFilesDir), "Assets");
  31. }
  32. private static string _UsingExternalFiles(params string[] subPath)
  33. {
  34. _EnsureInitialized();
  35. return System.IO.Path.Combine(new string[] { _TestFilesDir }.Concat(subPath).ToArray());
  36. }
  37. private static string _UsingInternalFiles(params string[] subPath)
  38. {
  39. _EnsureInitialized();
  40. return System.IO.Path.Combine(new string[] { _AssetFilesDir }.Concat(subPath).ToArray());
  41. }
  42. #endregion
  43. #region data
  44. /// <summary>
  45. /// Path to Tests/Assets/
  46. /// </summary>
  47. private static string _AssetFilesDir;
  48. /// <summary>
  49. /// Path to Tests/TestFiles/
  50. /// </summary>
  51. private static string _TestFilesDir;
  52. private static readonly string _SchemaDir = _UsingExternalFiles("glTF-Schema");
  53. private static readonly string _ValidationDir = _UsingExternalFiles("glTF-Validator");
  54. internal static readonly string _SampleModelsDir = _UsingExternalFiles("glTF-Sample-Models");
  55. private static readonly string _BabylonJsMeshesDir = _UsingExternalFiles("BabylonJS-Assets");
  56. private static readonly string _GeneratedModelsDir = _UsingExternalFiles("GeneratedReferenceModels", "v_0_6_1");
  57. #endregion
  58. #region properties
  59. public static string KhronosSampleModelsDirectory => _SampleModelsDir;
  60. #endregion
  61. #region API
  62. public static IReadOnlyList<string> GetSchemaExtensionsModelsPaths()
  63. {
  64. return GetModelPathsInDirectory(_SchemaDir, "extensions", "2.0");
  65. }
  66. public static IEnumerable<string> GetReferenceModelPaths(bool useNegative = false)
  67. {
  68. var dirPath = _GeneratedModelsDir;
  69. if (dirPath.EndsWith(".zip")) dirPath = dirPath.Substring(0, dirPath.Length - 4);
  70. var manifestsPath = System.IO.Path.Combine(dirPath, useNegative? "Negative" : "Positive");
  71. var manifests = System.IO.Directory.GetFiles(manifestsPath, "Manifest.json", System.IO.SearchOption.AllDirectories)
  72. .Skip(1)
  73. .ToArray();
  74. foreach (var m in manifests)
  75. {
  76. var d = System.IO.Path.GetDirectoryName(m);
  77. var content = System.IO.File.ReadAllText(m);
  78. var doc = Newtonsoft.Json.Linq.JObject.Parse(content);
  79. var models = doc.SelectToken("models");
  80. foreach(var model in models)
  81. {
  82. var mdlPath = (String)model.SelectToken("fileName");
  83. var loadable = !useNegative;
  84. if (loadable) loadable = (Boolean)model.SelectToken("loadable");
  85. mdlPath = System.IO.Path.Combine(d, mdlPath);
  86. yield return mdlPath;
  87. }
  88. }
  89. yield break;
  90. }
  91. public static IReadOnlyList<string> GetSampleModelsPaths()
  92. {
  93. var entries = KhronosSampleModel.Load();
  94. var files = entries
  95. .SelectMany(item => item.GetPaths(_SampleModelsDir, "2.0"))
  96. .ToList();
  97. return files;
  98. }
  99. public static IReadOnlyList<string> GetKhronosValidationPaths()
  100. {
  101. var skip = new string[]
  102. {
  103. "empty_object.gltf", // need to look further
  104. "custom_property.gltf",
  105. "integer_written_as_float.gltf",
  106. "unknown_type.gltf",
  107. "valid.gltf", // valid just because objects are unused
  108. "get_elements_sparse.gltf", // valid just because objects are unused
  109. "invalid_elements_float.gltf", // sure, it has invalid floats, but then the accessor is not used.
  110. "not_found.gltf", // it fails at a tricky time
  111. "non_relative_uri.gltf", // absolute path pointing to a http which is not supported.
  112. "unrecognized_format.gltf", // might require to dig into the image
  113. "multiple_extensions.gltf", // it's theoretically tracked (it should give a warning) but then, objects should not be empty...
  114. "invalid_tangent.gltf", // it's theoretically tracked (it should give a warning) but then, objects should not be empty...
  115. "primitive_incompatible_mode.gltf", // it's theoretically tracked (it should give a warning) but then, objects should not be empty...
  116. "primitive_no_position.gltf", // it's theoretically tracked (it should give a warning) but then, objects should not be empty...
  117. "index_buffer_degenerate_triangle.gltf", // it's theoretically tracked (it should give a warning) but then, objects should not be empty...
  118. "node_skinned_mesh_without_skin.gltf", // it's theoretically tracked (it should give a warning) but then, objects should not be empty...
  119. "duplicate_extension_entry.gltf",
  120. "named_objects.gltf", // gltf-validator says valid, but Buffer should not be.
  121. "unused_objects.gltf",
  122. "ignored_animated_transform.gltf", // an channel animated a node with a skin has no effect (warning) since nodes with skin have no transform
  123. "ignored_local_transform.gltf", // a transform in a node with a skin has no effect (warning) since nodes with skin have no transform
  124. "ignored_parent_transform.gltf", // a transform in a node with a skin has no effect (warning) since nodes with skin have no transform
  125. "misplaced_bin_chunk.glb",
  126. "valid_placeholder.glb",
  127. "undeclared_extension.gltf",
  128. "unexpected_extension.gltf",
  129. "unresolved_source.gltf",
  130. "unresolved_light_empty_root_ext.gltf",
  131. "unresolved_light_no_root_ext.gltf",
  132. "invalid_image_mime_type.gltf", // actual images cannot be validated
  133. };
  134. var files = GetModelPathsInDirectory(_ValidationDir, "test")
  135. .Where(item => skip.All(f=>!item.EndsWith(f)));
  136. return files
  137. .OrderBy(item => item)
  138. .ToList();
  139. }
  140. public static IReadOnlyList<string> GetBabylonJSModelsPaths()
  141. {
  142. var skipAlways = new string[]
  143. {
  144. "\\Elf\\Elf.gltf", // validator reports invalid inverse bind matrices.
  145. "\\meshes\\Tests\\AssetGenerator", // already covered separately.
  146. "\\meshes\\KHR_materials_volume_testing.glb", // draco compression-
  147. "\\meshes\\Yeti\\MayaExport\\", // validator reports out of bounds accesor
  148. "\\meshes\\Demos\\retargeting\\riggedMesh.glb", // validator reports errors
  149. "\\meshes\\Buildings\\road gap.glb", // uses KHR_Draco compression
  150. "\\meshes\\Buildings\\Road corner.glb", // uses KHR_Draco compression
  151. "\\meshes\\Tests\\BadDraco\\Box-draco.glb", // uses KHR_Draco compression
  152. };
  153. var files = GetModelPathsInDirectory(_BabylonJsMeshesDir);
  154. return files
  155. .Where(item => !item.ToUpperInvariant().Contains("GLTF-DRACO"))
  156. .Where(item => !item.ToUpperInvariant().Contains("GLTF-MESHOPT")) // not supported yet
  157. .Where(item => skipAlways.All(f => !item.Contains(f)))
  158. .OrderBy(item => item)
  159. .ToList();
  160. }
  161. public static string GetPollyFileModelPath()
  162. {
  163. return _UsingExternalFiles("glTF-Blender-Exporter", "polly", "project_polly.glb");
  164. }
  165. public static string GetUniVRMModelPath()
  166. {
  167. return _UsingExternalFiles("UniVRM", "AliciaSolid_vrm-0.51.vrm");
  168. }
  169. public static IEnumerable<string> GetMeshIntancingModelPaths()
  170. {
  171. var fromBabylon = GetBabylonJSModelsPaths()
  172. .Where(item => item.ToLowerInvariant().Contains("teapot"));
  173. var meshInstPath = _UsingInternalFiles("gltf-GpuMeshInstancing");
  174. var fromLocal = System.IO.Directory.GetFiles(meshInstPath, "*.glb", System.IO.SearchOption.AllDirectories);
  175. return fromBabylon.Concat(fromLocal);
  176. }
  177. private static IReadOnlyList<string> GetModelPathsInDirectory(params string[] paths)
  178. {
  179. var dirPath = System.IO.Path.Combine(paths);
  180. if (dirPath.EndsWith(".zip")) dirPath = dirPath.Substring(0, dirPath.Length-4);
  181. // if (!System.IO.Path.IsPathFullyQualified(dirPath)) throw new ArgumentException(nameof(dirPath));
  182. if (!System.IO.Path.IsPathRooted(dirPath)) throw new ArgumentException(nameof(dirPath));
  183. var gltf = System.IO.Directory.GetFiles(dirPath, "*.gltf", System.IO.SearchOption.AllDirectories);
  184. var glbb = System.IO.Directory.GetFiles(dirPath, "*.glb", System.IO.SearchOption.AllDirectories);
  185. return gltf.Concat(glbb).ToList();
  186. }
  187. #endregion
  188. }
  189. [System.Diagnostics.DebuggerDisplay("{Name}")]
  190. class KhronosSampleModel
  191. {
  192. #region loaders
  193. public static KhronosSampleModel[] Load()
  194. {
  195. var path = System.IO.Path.Combine(TestFiles._SampleModelsDir, "2.0", "model-index.json");
  196. var text = System.IO.File.ReadAllText(path);
  197. return Read(text);
  198. }
  199. public static KhronosSampleModel[] Read(string json)
  200. {
  201. var opts = new System.Text.Json.JsonSerializerOptions
  202. {
  203. PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
  204. PropertyNameCaseInsensitive = true
  205. };
  206. return System.Text.Json.JsonSerializer.Deserialize<KhronosSampleModel[]>(json, opts);
  207. }
  208. #endregion
  209. #region data
  210. public string Name { get; set; }
  211. public string Screenshot { get; set; }
  212. public Dictionary<string, string> Variants { get; set; } = new Dictionary<string, string>();
  213. #endregion
  214. #region API
  215. public IEnumerable<string> GetPaths(params string[] basePath)
  216. {
  217. var rootPath = System.IO.Path.Combine(basePath);
  218. foreach(var variant in Variants)
  219. {
  220. if (variant.Key == "glTF-Draco") continue; // draco is not supported by SharpGLTF
  221. yield return System.IO.Path.Combine(rootPath, Name, variant.Key, variant.Value);
  222. }
  223. }
  224. #endregion
  225. }
  226. }