basic_test.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. #include <algorithm>
  2. #include <cstdlib>
  3. #include <random>
  4. #include <catch2/catch_approx.hpp>
  5. #include <catch2/catch_test_macros.hpp>
  6. #include <catch2/benchmark/catch_benchmark.hpp>
  7. #include <glm/glm.hpp>
  8. #include <glm/gtc/type_ptr.hpp>
  9. #include <glm/gtx/quaternion.hpp>
  10. #include <glm/gtx/matrix_decompose.hpp>
  11. #include <fastgltf/base64.hpp>
  12. #include <fastgltf/parser.hpp>
  13. #include <fastgltf/types.hpp>
  14. #include "gltf_path.hpp"
  15. constexpr auto noOptions = fastgltf::Options::None;
  16. TEST_CASE("Component type tests", "[gltf-loader]") {
  17. using namespace fastgltf;
  18. // clang-format off
  19. REQUIRE(fastgltf::getNumComponents(AccessorType::Scalar) == 1);
  20. REQUIRE(fastgltf::getNumComponents(AccessorType::Vec2) == 2);
  21. REQUIRE(fastgltf::getNumComponents(AccessorType::Vec3) == 3);
  22. REQUIRE(fastgltf::getNumComponents(AccessorType::Vec4) == 4);
  23. REQUIRE(fastgltf::getNumComponents(AccessorType::Mat2) == 4);
  24. REQUIRE(fastgltf::getNumComponents(AccessorType::Mat3) == 9);
  25. REQUIRE(fastgltf::getNumComponents(AccessorType::Mat4) == 16);
  26. REQUIRE(fastgltf::getComponentBitSize(ComponentType::Byte) == 8);
  27. REQUIRE(fastgltf::getComponentBitSize(ComponentType::UnsignedByte) == 8);
  28. REQUIRE(fastgltf::getComponentBitSize(ComponentType::Short) == 16);
  29. REQUIRE(fastgltf::getComponentBitSize(ComponentType::UnsignedShort) == 16);
  30. REQUIRE(fastgltf::getComponentBitSize(ComponentType::UnsignedInt) == 32);
  31. REQUIRE(fastgltf::getComponentBitSize(ComponentType::Float) == 32);
  32. REQUIRE(fastgltf::getComponentBitSize(ComponentType::Double) == 64);
  33. REQUIRE(fastgltf::getComponentBitSize(ComponentType::Invalid) == 0);
  34. REQUIRE(fastgltf::getElementByteSize(AccessorType::Scalar, ComponentType::Byte) == 1);
  35. REQUIRE(fastgltf::getElementByteSize(AccessorType::Vec4, ComponentType::Byte) == 4);
  36. REQUIRE(fastgltf::getElementByteSize(AccessorType::Vec4, ComponentType::Short) == 8);
  37. REQUIRE(fastgltf::getComponentType(5120) == ComponentType::Byte);
  38. REQUIRE(fastgltf::getComponentType(5121) == ComponentType::UnsignedByte);
  39. REQUIRE(fastgltf::getComponentType(5122) == ComponentType::Short);
  40. REQUIRE(fastgltf::getComponentType(5123) == ComponentType::UnsignedShort);
  41. REQUIRE(fastgltf::getComponentType(5125) == ComponentType::UnsignedInt);
  42. REQUIRE(fastgltf::getComponentType(5126) == ComponentType::Float);
  43. REQUIRE(fastgltf::getComponentType(5130) == ComponentType::Double);
  44. REQUIRE(fastgltf::getComponentType(5131) == ComponentType::Invalid);
  45. // clang-format on
  46. }
  47. TEST_CASE("Test all variants of CRC32-C hashing", "[gltf-loader]") {
  48. // TODO: Determine SSE4.2 support here.
  49. for (std::size_t i = 0; i < 256; ++i) {
  50. // Generate a random string up to 256 chars long.
  51. static constexpr std::string_view chars =
  52. "0123456789"
  53. "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  54. "abcdefghijklmnopqrstuvwxyz";
  55. static std::mt19937 rng(std::random_device{}());
  56. static std::uniform_int_distribution<std::string::size_type> pick(0, chars.size() - 1);
  57. std::string str(i, '\0');
  58. for (std::size_t j = 0; j < i; ++j)
  59. str[j] = chars[pick(rng)];
  60. #if defined(__x86_64__) || defined(_M_AMD64) || defined(_M_IX86)
  61. // We'll try and test if the hardware accelerated version generates the same, correct results.
  62. REQUIRE(fastgltf::crc32c(str) == fastgltf::hwcrc32c(str));
  63. #endif
  64. }
  65. }
  66. TEST_CASE("Test extension stringification", "[gltf-loader]") {
  67. auto stringified = stringifyExtension(fastgltf::Extensions::EXT_meshopt_compression);
  68. REQUIRE(stringified == fastgltf::extensions::EXT_meshopt_compression);
  69. stringified = stringifyExtension(fastgltf::Extensions::EXT_meshopt_compression | fastgltf::Extensions::EXT_texture_webp);
  70. REQUIRE(stringified == fastgltf::extensions::EXT_meshopt_compression);
  71. }
  72. TEST_CASE("Test if glTF type detection works", "[gltf-loader]") {
  73. fastgltf::Parser parser;
  74. SECTION("glTF") {
  75. auto gltfPath = sampleModels / "2.0" / "ABeautifulGame" / "glTF";
  76. REQUIRE(std::filesystem::exists(gltfPath));
  77. fastgltf::GltfDataBuffer jsonData;
  78. REQUIRE(jsonData.loadFromFile(gltfPath / "ABeautifulGame.gltf"));
  79. REQUIRE(fastgltf::determineGltfFileType(&jsonData) == fastgltf::GltfType::glTF);
  80. auto model = parser.loadGLTF(&jsonData, gltfPath);
  81. REQUIRE(model.error() == fastgltf::Error::None);
  82. REQUIRE(model.get_if() != nullptr);
  83. REQUIRE(fastgltf::validate(model.get()) == fastgltf::Error::None);
  84. }
  85. SECTION("GLB") {
  86. auto glbPath = sampleModels / "2.0" / "BoomBox" / "glTF-Binary";
  87. REQUIRE(std::filesystem::exists(glbPath));
  88. fastgltf::GltfDataBuffer jsonData;
  89. REQUIRE(jsonData.loadFromFile(glbPath / "BoomBox.glb"));
  90. REQUIRE(fastgltf::determineGltfFileType(&jsonData) == fastgltf::GltfType::GLB);
  91. auto model = parser.loadBinaryGLTF(&jsonData, glbPath);
  92. REQUIRE(model.error() == fastgltf::Error::None);
  93. REQUIRE(model.get_if() != nullptr);
  94. }
  95. SECTION("Invalid") {
  96. auto gltfPath = path / "base64.txt"; // Random file in the test directory that's not a glTF file.
  97. REQUIRE(std::filesystem::exists(gltfPath));
  98. fastgltf::GltfDataBuffer jsonData;
  99. REQUIRE(jsonData.loadFromFile(gltfPath));
  100. REQUIRE(fastgltf::determineGltfFileType(&jsonData) == fastgltf::GltfType::Invalid);
  101. }
  102. }
  103. TEST_CASE("Loading some basic glTF", "[gltf-loader]") {
  104. fastgltf::Parser parser;
  105. SECTION("Loading basic invalid glTF files") {
  106. auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  107. REQUIRE(jsonData->loadFromFile(path / "empty_json.gltf"));
  108. auto emptyGltf = parser.loadGLTF(jsonData.get(), path);
  109. REQUIRE(emptyGltf.error() == fastgltf::Error::InvalidOrMissingAssetField);
  110. }
  111. SECTION("Load basic glTF file") {
  112. auto basicJsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  113. REQUIRE(basicJsonData->loadFromFile(path / "basic_gltf.gltf"));
  114. auto basicGltf = parser.loadGLTF(basicJsonData.get(), path);
  115. REQUIRE(basicGltf.error() == fastgltf::Error::None);
  116. REQUIRE(fastgltf::validate(basicGltf.get()) == fastgltf::Error::None);
  117. }
  118. SECTION("Loading basic Cube.gltf") {
  119. auto cubePath = sampleModels / "2.0" / "Cube" / "glTF";
  120. auto cubeJsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  121. REQUIRE(cubeJsonData->loadFromFile(cubePath / "Cube.gltf"));
  122. auto cube = parser.loadGLTF(cubeJsonData.get(), cubePath, noOptions, fastgltf::Category::OnlyRenderable);
  123. REQUIRE(cube.error() == fastgltf::Error::None);
  124. REQUIRE(fastgltf::validate(cube.get()) == fastgltf::Error::None);
  125. REQUIRE(cube->scenes.size() == 1);
  126. REQUIRE(cube->scenes.front().nodeIndices.size() == 1);
  127. REQUIRE(cube->scenes.front().nodeIndices.front() == 0);
  128. REQUIRE(cube->nodes.size() == 1);
  129. REQUIRE(cube->nodes.front().name == "Cube");
  130. REQUIRE(std::holds_alternative<fastgltf::Node::TRS>(cube->nodes.front().transform));
  131. REQUIRE(cube->accessors.size() == 5);
  132. REQUIRE(cube->accessors[0].type == fastgltf::AccessorType::Scalar);
  133. REQUIRE(cube->accessors[0].componentType == fastgltf::ComponentType::UnsignedShort);
  134. REQUIRE(cube->accessors[1].type == fastgltf::AccessorType::Vec3);
  135. REQUIRE(cube->accessors[1].componentType == fastgltf::ComponentType::Float);
  136. REQUIRE(cube->bufferViews.size() == 5);
  137. REQUIRE(cube->buffers.size() == 1);
  138. REQUIRE(cube->materials.size() == 1);
  139. auto& material = cube->materials.front();
  140. REQUIRE(material.name == "Cube");
  141. REQUIRE(material.pbrData.baseColorTexture.has_value());
  142. REQUIRE(material.pbrData.baseColorTexture->textureIndex == 0);
  143. REQUIRE(material.pbrData.metallicRoughnessTexture.has_value());
  144. REQUIRE(material.pbrData.metallicRoughnessTexture->textureIndex == 1);
  145. REQUIRE(!material.normalTexture.has_value());
  146. REQUIRE(!material.emissiveTexture.has_value());
  147. REQUIRE(!material.occlusionTexture.has_value());
  148. }
  149. SECTION("Loading basic Box.gltf") {
  150. auto boxPath = sampleModels / "2.0" / "Box" / "glTF";
  151. auto boxJsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  152. REQUIRE(boxJsonData->loadFromFile(boxPath / "Box.gltf"));
  153. auto box = parser.loadGLTF(boxJsonData.get(), boxPath, noOptions, fastgltf::Category::OnlyRenderable);
  154. REQUIRE(box.error() == fastgltf::Error::None);
  155. REQUIRE(fastgltf::validate(box.get()) == fastgltf::Error::None);
  156. REQUIRE(box->defaultScene.has_value());
  157. REQUIRE(box->defaultScene.value() == 0);
  158. REQUIRE(box->nodes.size() == 2);
  159. REQUIRE(box->nodes[0].children.size() == 1);
  160. REQUIRE(box->nodes[0].children[0] == 1);
  161. REQUIRE(box->nodes[1].children.empty());
  162. REQUIRE(box->nodes[1].meshIndex.has_value());
  163. REQUIRE(box->nodes[1].meshIndex.value() == 0);
  164. REQUIRE(box->materials.size() == 1);
  165. REQUIRE(box->materials[0].name == "Red");
  166. REQUIRE(box->materials[0].pbrData.baseColorFactor[3] == 1.0f);
  167. REQUIRE(box->materials[0].pbrData.metallicFactor == 0.0f);
  168. }
  169. }
  170. TEST_CASE("Loading glTF animation", "[gltf-loader]") {
  171. auto animatedCube = sampleModels / "2.0" / "AnimatedCube" / "glTF";
  172. auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  173. REQUIRE(jsonData->loadFromFile(animatedCube / "AnimatedCube.gltf"));
  174. fastgltf::Parser parser;
  175. auto asset = parser.loadGLTF(jsonData.get(), animatedCube, noOptions, fastgltf::Category::OnlyAnimations);
  176. REQUIRE(asset.error() == fastgltf::Error::None);
  177. REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
  178. REQUIRE(!asset->animations.empty());
  179. auto& animation = asset->animations.front();
  180. REQUIRE(animation.name == "animation_AnimatedCube");
  181. REQUIRE(!animation.channels.empty());
  182. REQUIRE(animation.channels.front().nodeIndex == 0);
  183. REQUIRE(animation.channels.front().samplerIndex == 0);
  184. REQUIRE(animation.channels.front().path == fastgltf::AnimationPath::Rotation);
  185. REQUIRE(!animation.samplers.empty());
  186. REQUIRE(animation.samplers.front().interpolation == fastgltf::AnimationInterpolation::Linear);
  187. REQUIRE(animation.samplers.front().inputAccessor == 0);
  188. REQUIRE(animation.samplers.front().outputAccessor == 1);
  189. }
  190. TEST_CASE("Loading glTF skins", "[gltf-loader]") {
  191. auto simpleSkin = sampleModels / "2.0" / "SimpleSkin" / "glTF";
  192. auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  193. REQUIRE(jsonData->loadFromFile(simpleSkin / "SimpleSkin.gltf"));
  194. fastgltf::Parser parser;
  195. auto asset = parser.loadGLTF(jsonData.get(), simpleSkin, noOptions, fastgltf::Category::Skins | fastgltf::Category::Nodes);
  196. REQUIRE(asset.error() == fastgltf::Error::None);
  197. REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
  198. REQUIRE(!asset->skins.empty());
  199. auto& skin = asset->skins.front();
  200. REQUIRE(skin.joints.size() == 2);
  201. REQUIRE(skin.joints[0] == 1);
  202. REQUIRE(skin.joints[1] == 2);
  203. REQUIRE(skin.inverseBindMatrices.has_value());
  204. REQUIRE(skin.inverseBindMatrices.value() == 4);
  205. REQUIRE(!asset->nodes.empty());
  206. auto& node = asset->nodes.front();
  207. REQUIRE(node.skinIndex.has_value());
  208. REQUIRE(node.skinIndex == 0);
  209. }
  210. TEST_CASE("Loading glTF cameras", "[gltf-loader]") {
  211. auto cameras = sampleModels / "2.0" / "Cameras" / "glTF";
  212. auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  213. REQUIRE(jsonData->loadFromFile(cameras / "Cameras.gltf"));
  214. fastgltf::Parser parser;
  215. auto asset = parser.loadGLTF(jsonData.get(), cameras, noOptions, fastgltf::Category::Cameras);
  216. REQUIRE(asset.error() == fastgltf::Error::None);
  217. REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
  218. REQUIRE(asset->cameras.size() == 2);
  219. REQUIRE(std::holds_alternative<fastgltf::Camera::Perspective>(asset->cameras[0].camera));
  220. REQUIRE(std::holds_alternative<fastgltf::Camera::Orthographic>(asset->cameras[1].camera));
  221. const auto* pPerspective = std::get_if<fastgltf::Camera::Perspective>(&asset->cameras[0].camera);
  222. REQUIRE(pPerspective != nullptr);
  223. REQUIRE(pPerspective->aspectRatio == 1.0f);
  224. REQUIRE(pPerspective->yfov == 0.7f);
  225. REQUIRE(pPerspective->zfar == 100);
  226. REQUIRE(pPerspective->znear == 0.01f);
  227. const auto* pOrthographic = std::get_if<fastgltf::Camera::Orthographic>(&asset->cameras[1].camera);
  228. REQUIRE(pOrthographic != nullptr);
  229. REQUIRE(pOrthographic->xmag == 1.0f);
  230. REQUIRE(pOrthographic->ymag == 1.0f);
  231. REQUIRE(pOrthographic->zfar == 100);
  232. REQUIRE(pOrthographic->znear == 0.01f);
  233. }
  234. TEST_CASE("Validate whole glTF", "[gltf-loader]") {
  235. auto sponza = sampleModels / "2.0" / "Sponza" / "glTF";
  236. auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  237. REQUIRE(jsonData->loadFromFile(sponza / "Sponza.gltf"));
  238. fastgltf::Parser parser;
  239. auto model = parser.loadGLTF(jsonData.get(), sponza);
  240. REQUIRE(model.error() == fastgltf::Error::None);
  241. REQUIRE(fastgltf::validate(model.get()) == fastgltf::Error::None);
  242. auto brainStem = sampleModels / "2.0" / "BrainStem" / "glTF";
  243. jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  244. REQUIRE(jsonData->loadFromFile(brainStem / "BrainStem.gltf"));
  245. auto model2 = parser.loadGLTF(jsonData.get(), brainStem);
  246. REQUIRE(model2.error() == fastgltf::Error::None);
  247. REQUIRE(fastgltf::validate(model2.get()) == fastgltf::Error::None);
  248. }
  249. TEST_CASE("Test allocation callbacks for embedded buffers", "[gltf-loader]") {
  250. auto boxPath = sampleModels / "2.0" / "Box" / "glTF-Embedded";
  251. auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  252. REQUIRE(jsonData->loadFromFile(boxPath / "Box.gltf"));
  253. std::vector<void*> allocations;
  254. auto mapCallback = [](uint64_t bufferSize, void* userPointer) -> fastgltf::BufferInfo {
  255. REQUIRE(userPointer != nullptr);
  256. auto* allocations = static_cast<std::vector<void*>*>(userPointer);
  257. allocations->emplace_back(std::malloc(bufferSize));
  258. return fastgltf::BufferInfo {
  259. allocations->back(),
  260. allocations->size() - 1,
  261. };
  262. };
  263. fastgltf::Parser parser;
  264. parser.setUserPointer(&allocations);
  265. parser.setBufferAllocationCallback(mapCallback, nullptr);
  266. auto asset = parser.loadGLTF(jsonData.get(), boxPath, noOptions, fastgltf::Category::Buffers);
  267. REQUIRE(asset.error() == fastgltf::Error::None);
  268. REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
  269. REQUIRE(allocations.size() == 1);
  270. REQUIRE(asset->buffers.size() == 1);
  271. auto& buffer = asset->buffers.front();
  272. const auto* customBuffer = std::get_if<fastgltf::sources::CustomBuffer>(&buffer.data);
  273. REQUIRE(customBuffer != nullptr);
  274. REQUIRE(customBuffer->id == 0);
  275. for (auto& allocation : allocations) {
  276. REQUIRE(allocation != nullptr);
  277. std::free(allocation);
  278. }
  279. }
  280. TEST_CASE("Test base64 decoding callbacks", "[gltf-loader]") {
  281. auto boxPath = sampleModels / "2.0" / "Box" / "glTF-Embedded";
  282. auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  283. REQUIRE(jsonData->loadFromFile(boxPath / "Box.gltf"));
  284. size_t decodeCounter = 0;
  285. auto decodeCallback = [](std::string_view encodedData, uint8_t* outputData, size_t padding, size_t outputSize, void* userPointer) {
  286. (*static_cast<size_t*>(userPointer))++;
  287. fastgltf::base64::decode_inplace(encodedData, outputData, padding);
  288. };
  289. fastgltf::Parser parser;
  290. parser.setUserPointer(&decodeCounter);
  291. parser.setBase64DecodeCallback(decodeCallback);
  292. auto model = parser.loadGLTF(jsonData.get(), boxPath, noOptions, fastgltf::Category::Buffers);
  293. REQUIRE(model.error() == fastgltf::Error::None);
  294. REQUIRE(fastgltf::validate(model.get()) == fastgltf::Error::None);
  295. REQUIRE(decodeCounter != 0);
  296. }
  297. TEST_CASE("Test TRS parsing and optional decomposition", "[gltf-loader]") {
  298. SECTION("Test decomposition on glTF asset") {
  299. auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  300. REQUIRE(jsonData->loadFromFile(path / "transform_matrices.gltf"));
  301. // Parse once without decomposing, once with decomposing the matrix.
  302. fastgltf::Parser parser;
  303. auto assetWithMatrix = parser.loadGLTF(jsonData.get(), path, noOptions, fastgltf::Category::Nodes | fastgltf::Category::Cameras);
  304. REQUIRE(assetWithMatrix.error() == fastgltf::Error::None);
  305. REQUIRE(fastgltf::validate(assetWithMatrix.get()) == fastgltf::Error::None);
  306. auto assetDecomposed = parser.loadGLTF(jsonData.get(), path, fastgltf::Options::DecomposeNodeMatrices, fastgltf::Category::Nodes | fastgltf::Category::Cameras);
  307. REQUIRE(assetDecomposed.error() == fastgltf::Error::None);
  308. REQUIRE(fastgltf::validate(assetDecomposed.get()) == fastgltf::Error::None);
  309. REQUIRE(assetWithMatrix->cameras.size() == 1);
  310. REQUIRE(assetDecomposed->cameras.size() == 1);
  311. REQUIRE(assetWithMatrix->nodes.size() == 2);
  312. REQUIRE(assetDecomposed->nodes.size() == 2);
  313. REQUIRE(std::holds_alternative<fastgltf::Node::TransformMatrix>(assetWithMatrix->nodes.back().transform));
  314. REQUIRE(std::holds_alternative<fastgltf::Node::TRS>(assetDecomposed->nodes.back().transform));
  315. // Get the TRS components from the first node and use them as the test data for decomposing.
  316. const auto* pDefaultTRS = std::get_if<fastgltf::Node::TRS>(&assetWithMatrix->nodes.front().transform);
  317. REQUIRE(pDefaultTRS != nullptr);
  318. auto translation = glm::make_vec3(pDefaultTRS->translation.data());
  319. auto rotation = glm::make_quat(pDefaultTRS->rotation.data());
  320. auto scale = glm::make_vec3(pDefaultTRS->scale.data());
  321. auto rotationMatrix = glm::toMat4(rotation);
  322. auto transform = glm::translate(glm::mat4(1.0f), translation) * rotationMatrix * glm::scale(glm::mat4(1.0f), scale);
  323. // Check if the parsed matrix is correct.
  324. const auto* pMatrix = std::get_if<fastgltf::Node::TransformMatrix>(&assetWithMatrix->nodes.back().transform);
  325. REQUIRE(pMatrix != nullptr);
  326. REQUIRE(glm::make_mat4x4(pMatrix->data()) == transform);
  327. // Check if the decomposed components equal the original components.
  328. const auto* pDecomposedTRS = std::get_if<fastgltf::Node::TRS>(&assetDecomposed->nodes.back().transform);
  329. REQUIRE(glm::make_vec3(pDecomposedTRS->translation.data()) == translation);
  330. REQUIRE(glm::make_quat(pDecomposedTRS->rotation.data()) == rotation);
  331. REQUIRE(glm::make_vec3(pDecomposedTRS->scale.data()) == scale);
  332. }
  333. SECTION("Test decomposition against glm decomposition") {
  334. // Some random complex transform matrix from one of the glTF sample models.
  335. std::array<float, 16> matrix = {
  336. -0.4234085381031037F,
  337. -0.9059388637542724F,
  338. -7.575183536001616e-11F,
  339. 0.0F,
  340. -0.9059388637542724F,
  341. 0.4234085381031037F,
  342. -4.821281221478735e-11F,
  343. 0.0F,
  344. 7.575183536001616e-11F,
  345. 4.821281221478735e-11F,
  346. -1.0F,
  347. 0.0F,
  348. -90.59386444091796F,
  349. -24.379817962646489F,
  350. -40.05522918701172F,
  351. 1.0F
  352. };
  353. std::array<float, 3> translation = {}, scale = {};
  354. std::array<float, 4> rotation = {};
  355. fastgltf::decomposeTransformMatrix(matrix, scale, rotation, translation);
  356. auto glmMatrix = glm::make_mat4x4(matrix.data());
  357. glm::vec3 glmScale, glmTranslation, glmSkew;
  358. glm::quat glmRotation;
  359. glm::vec4 glmPerspective;
  360. glm::decompose(glmMatrix, glmScale, glmRotation, glmTranslation, glmSkew, glmPerspective);
  361. // I use glm::epsilon<float>() * 10 here because some matrices I tested this with resulted
  362. // in an error margin greater than the normal epsilon value. I will investigate this in the
  363. // future, but I suspect using double in the decompose functions should help mitigate most
  364. // of it.
  365. REQUIRE(glm::make_vec3(translation.data()) == glmTranslation);
  366. REQUIRE(glm::all(glm::epsilonEqual(glm::make_quat(rotation.data()), glmRotation, glm::epsilon<float>() * 10)));
  367. REQUIRE(glm::all(glm::epsilonEqual(glm::make_vec3(scale.data()), glmScale, glm::epsilon<float>())));
  368. }
  369. }
  370. TEST_CASE("Validate sparse accessor parsing", "[gltf-loader]") {
  371. auto simpleSparseAccessor = sampleModels / "2.0" / "SimpleSparseAccessor" / "glTF";
  372. auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  373. REQUIRE(jsonData->loadFromFile(simpleSparseAccessor / "SimpleSparseAccessor.gltf"));
  374. fastgltf::Parser parser;
  375. auto asset = parser.loadGLTF(jsonData.get(), simpleSparseAccessor, noOptions, fastgltf::Category::Accessors);
  376. REQUIRE(asset.error() == fastgltf::Error::None);
  377. REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
  378. REQUIRE(asset->accessors.size() == 2);
  379. REQUIRE(!asset->accessors[0].sparse.has_value());
  380. REQUIRE(asset->accessors[1].sparse.has_value());
  381. auto& sparse = asset->accessors[1].sparse.value();
  382. REQUIRE(sparse.count == 3);
  383. REQUIRE(sparse.indicesBufferView == 2);
  384. REQUIRE(sparse.indicesByteOffset == 0);
  385. REQUIRE(sparse.valuesBufferView == 3);
  386. REQUIRE(sparse.valuesByteOffset == 0);
  387. REQUIRE(sparse.indexComponentType == fastgltf::ComponentType::UnsignedShort);
  388. }
  389. TEST_CASE("Validate morph target parsing", "[gltf-loader]") {
  390. auto simpleMorph = sampleModels / "2.0" / "SimpleMorph" / "glTF";
  391. auto jsonData = std::make_unique<fastgltf::GltfDataBuffer>();
  392. REQUIRE(jsonData->loadFromFile(simpleMorph / "SimpleMorph.gltf"));
  393. fastgltf::Parser parser;
  394. auto asset = parser.loadGLTF(jsonData.get(), simpleMorph, noOptions, fastgltf::Category::Meshes);
  395. REQUIRE(asset.error() == fastgltf::Error::None);
  396. REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
  397. REQUIRE(asset->meshes.size() == 1);
  398. REQUIRE(asset->meshes.front().weights.size() == 2);
  399. REQUIRE(asset->meshes.front().primitives.size() == 1);
  400. auto& primitive = asset->meshes.front().primitives.front();
  401. auto position = primitive.findAttribute("POSITION");
  402. REQUIRE(position != primitive.attributes.end());
  403. REQUIRE((*position).second == 1);
  404. REQUIRE(primitive.targets.size() == 2);
  405. auto positionTarget0 = primitive.findTargetAttribute(0, "POSITION");
  406. REQUIRE(positionTarget0 != primitive.targets[0].end());
  407. REQUIRE((*positionTarget0).second == 2);
  408. auto positionTarget1 = primitive.findTargetAttribute(1, "POSITION");
  409. REQUIRE(positionTarget0 != primitive.targets[1].end());
  410. REQUIRE((*positionTarget1).second == 3);
  411. }
  412. TEST_CASE("Test accessors min/max", "[gltf-loader]") {
  413. auto lightsLamp = sampleModels / "2.0" / "LightsPunctualLamp" / "glTF";
  414. fastgltf::GltfDataBuffer jsonData;
  415. REQUIRE(jsonData.loadFromFile(lightsLamp / "LightsPunctualLamp.gltf"));
  416. fastgltf::Parser parser(fastgltf::Extensions::KHR_lights_punctual);
  417. auto asset = parser.loadGLTF(&jsonData, lightsLamp, noOptions, fastgltf::Category::Accessors);
  418. REQUIRE(asset.error() == fastgltf::Error::None);
  419. REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
  420. REQUIRE(std::find_if(asset->extensionsUsed.begin(), asset->extensionsUsed.end(), [](auto& string) {
  421. return string == fastgltf::extensions::KHR_lights_punctual;
  422. }) != asset->extensionsUsed.end());
  423. REQUIRE(asset->accessors.size() == 15);
  424. auto& accessors = asset->accessors;
  425. {
  426. auto& firstAccessor = accessors[0];
  427. const auto* max = std::get_if<std::pmr::vector<std::int64_t>>(&firstAccessor.max);
  428. const auto* min = std::get_if<std::pmr::vector<std::int64_t>>(&firstAccessor.min);
  429. REQUIRE(max != nullptr);
  430. REQUIRE(min != nullptr);
  431. REQUIRE(max->size() == fastgltf::getNumComponents(firstAccessor.type));
  432. REQUIRE(max->size() == 1);
  433. REQUIRE(min->size() == 1);
  434. REQUIRE(max->front() == 3211);
  435. REQUIRE(min->front() == 0);
  436. }
  437. {
  438. auto& secondAccessor = accessors[1];
  439. const auto* max = std::get_if<std::pmr::vector<double>>(&secondAccessor.max);
  440. const auto* min = std::get_if<std::pmr::vector<double>>(&secondAccessor.min);
  441. REQUIRE(max != nullptr);
  442. REQUIRE(min != nullptr);
  443. REQUIRE(max->size() == fastgltf::getNumComponents(secondAccessor.type));
  444. REQUIRE(max->size() == 3);
  445. REQUIRE(min->size() == 3);
  446. REQUIRE(glm::epsilonEqual(max->at(0), 0.81497824192047119, glm::epsilon<double>()));
  447. REQUIRE(glm::epsilonEqual(max->at(1), 1.8746249675750732, glm::epsilon<double>()));
  448. REQUIRE(glm::epsilonEqual(max->at(2), 0.32295516133308411, glm::epsilon<double>()));
  449. REQUIRE(glm::epsilonEqual(min->at(0), -0.12269512563943863, glm::epsilon<double>()));
  450. REQUIRE(glm::epsilonEqual(min->at(1), 0.013025385327637196, glm::epsilon<double>()));
  451. REQUIRE(glm::epsilonEqual(min->at(2), -0.32393229007720947, glm::epsilon<double>()));
  452. }
  453. {
  454. auto& fifthAccessor = accessors[4];
  455. const auto* max = std::get_if<std::pmr::vector<double>>(&fifthAccessor.max);
  456. const auto* min = std::get_if<std::pmr::vector<double>>(&fifthAccessor.min);
  457. REQUIRE(max != nullptr);
  458. REQUIRE(min != nullptr);
  459. REQUIRE(max->size() == fastgltf::getNumComponents(fifthAccessor.type));
  460. REQUIRE(max->size() == 4);
  461. REQUIRE(min->size() == 4);
  462. REQUIRE(max->back() == 1.0);
  463. }
  464. }
  465. TEST_CASE("Test unicode characters", "[gltf-loader]") {
  466. auto lightsLamp = sampleModels / "2.0" / std::filesystem::u8path(u8"Unicode❤♻Test") / "glTF";
  467. fastgltf::GltfDataBuffer jsonData;
  468. REQUIRE(jsonData.loadFromFile(lightsLamp / std::filesystem::u8path(u8"Unicode❤♻Test.gltf")));
  469. fastgltf::Parser parser;
  470. auto asset = parser.loadGLTF(&jsonData, lightsLamp);
  471. REQUIRE(asset.error() == fastgltf::Error::None);
  472. REQUIRE(fastgltf::validate(asset.get()) == fastgltf::Error::None);
  473. REQUIRE(!asset->materials.empty());
  474. REQUIRE(asset->materials[0].name == u8"Unicode❤♻Material");
  475. REQUIRE(!asset->buffers.empty());
  476. auto bufferUri = std::get<fastgltf::sources::URI>(asset->buffers[0].data);
  477. REQUIRE(bufferUri.uri.path() == u8"Unicode❤♻Binary.bin");
  478. }