usd_animations-on-top-of-v5.4.3.patch 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080
  1. diff --git a/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp b/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp
  2. index 3e32917f9..b4fd6a51b 100644
  3. --- a/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp
  4. +++ b/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp
  5. @@ -58,6 +58,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  6. #include <assimp/importerdesc.h>
  7. #include <assimp/IOStreamBuffer.h>
  8. #include <assimp/IOSystem.hpp>
  9. +#include "assimp/MemoryIOWrapper.h"
  10. #include <assimp/StringUtils.h>
  11. #include <assimp/StreamReader.h>
  12. @@ -81,7 +82,7 @@ using namespace std;
  13. void USDImporterImplTinyusdz::InternReadFile(
  14. const std::string &pFile,
  15. aiScene *pScene,
  16. - IOSystem *) {
  17. + IOSystem *pIOHandler) {
  18. // Grab filename for logging purposes
  19. size_t pos = pFile.find_last_of('/');
  20. string basePath = pFile.substr(0, pos);
  21. @@ -91,29 +92,48 @@ void USDImporterImplTinyusdz::InternReadFile(
  22. ss << "InternReadFile(): model" << nameWExt;
  23. TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
  24. + bool is_load_from_mem{ pFile.substr(0, AI_MEMORYIO_MAGIC_FILENAME_LENGTH) == AI_MEMORYIO_MAGIC_FILENAME };
  25. + std::vector<uint8_t> in_mem_data;
  26. + if (is_load_from_mem) {
  27. + auto stream_closer = [pIOHandler](IOStream *pStream) {
  28. + pIOHandler->Close(pStream);
  29. + };
  30. + std::unique_ptr<IOStream, decltype(stream_closer)> file_stream(pIOHandler->Open(pFile, "rb"), stream_closer);
  31. + if (!file_stream) {
  32. + throw DeadlyImportError("Failed to open file ", pFile, ".");
  33. + }
  34. + size_t file_size{ file_stream->FileSize() };
  35. + in_mem_data.resize(file_size);
  36. + file_stream->Read(in_mem_data.data(), 1, file_size);
  37. + }
  38. +
  39. bool ret{ false };
  40. tinyusdz::USDLoadOptions options;
  41. tinyusdz::Stage stage;
  42. std::string warn, err;
  43. bool is_usdz{ false };
  44. if (isUsdc(pFile)) {
  45. - ret = LoadUSDCFromFile(pFile, &stage, &warn, &err, options);
  46. + ret = is_load_from_mem ? LoadUSDCFromMemory(in_mem_data.data(), in_mem_data.size(), pFile, &stage, &warn, &err, options) :
  47. + LoadUSDCFromFile(pFile, &stage, &warn, &err, options);
  48. ss.str("");
  49. ss << "InternReadFile(): LoadUSDCFromFile() result: " << ret;
  50. TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
  51. } else if (isUsda(pFile)) {
  52. - ret = LoadUSDAFromFile(pFile, &stage, &warn, &err, options);
  53. + ret = is_load_from_mem ? LoadUSDAFromMemory(in_mem_data.data(), in_mem_data.size(), pFile, &stage, &warn, &err, options) :
  54. + LoadUSDAFromFile(pFile, &stage, &warn, &err, options);
  55. ss.str("");
  56. ss << "InternReadFile(): LoadUSDAFromFile() result: " << ret;
  57. TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
  58. } else if (isUsdz(pFile)) {
  59. - ret = LoadUSDZFromFile(pFile, &stage, &warn, &err, options);
  60. + ret = is_load_from_mem ? LoadUSDZFromMemory(in_mem_data.data(), in_mem_data.size(), pFile, &stage, &warn, &err, options) :
  61. + LoadUSDZFromFile(pFile, &stage, &warn, &err, options);
  62. is_usdz = true;
  63. ss.str("");
  64. ss << "InternReadFile(): LoadUSDZFromFile() result: " << ret;
  65. TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
  66. } else if (isUsd(pFile)) {
  67. - ret = LoadUSDFromFile(pFile, &stage, &warn, &err, options);
  68. + ret = is_load_from_mem ? LoadUSDFromMemory(in_mem_data.data(), in_mem_data.size(), pFile, &stage, &warn, &err, options) :
  69. + LoadUSDFromFile(pFile, &stage, &warn, &err, options);
  70. ss.str("");
  71. ss << "InternReadFile(): LoadUSDFromFile() result: " << ret;
  72. TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
  73. @@ -149,7 +169,9 @@ void USDImporterImplTinyusdz::InternReadFile(
  74. // NOTE: Pointer address of usdz_asset must be valid until the call of RenderSceneConverter::ConvertToRenderScene.
  75. tinyusdz::USDZAsset usdz_asset;
  76. if (is_usdz) {
  77. - if (!tinyusdz::ReadUSDZAssetInfoFromFile(pFile, &usdz_asset, &warn, &err)) {
  78. + bool is_read_USDZ_asset = is_load_from_mem ? tinyusdz::ReadUSDZAssetInfoFromMemory(in_mem_data.data(), in_mem_data.size(), false, &usdz_asset, &warn, &err) :
  79. + tinyusdz::ReadUSDZAssetInfoFromFile(pFile, &usdz_asset, &warn, &err);
  80. + if (!is_read_USDZ_asset) {
  81. if (!warn.empty()) {
  82. ss.str("");
  83. ss << "InternReadFile(): ReadUSDZAssetInfoFromFile: WARNING reported: " << warn;
  84. @@ -190,18 +212,140 @@ void USDImporterImplTinyusdz::InternReadFile(
  85. return;
  86. }
  87. -// sanityCheckNodesRecursive(pScene->mRootNode);
  88. + // sanityCheckNodesRecursive(pScene->mRootNode);
  89. + animations(render_scene, pScene);
  90. meshes(render_scene, pScene, nameWExt);
  91. materials(render_scene, pScene, nameWExt);
  92. textures(render_scene, pScene, nameWExt);
  93. textureImages(render_scene, pScene, nameWExt);
  94. buffers(render_scene, pScene, nameWExt);
  95. -
  96. - std::map<size_t, tinyusdz::tydra::Node> meshNodes;
  97. - setupNodes(render_scene, pScene, meshNodes, nameWExt);
  98. + pScene->mRootNode = nodesRecursive(nullptr, render_scene.nodes[0], render_scene.skeletons);
  99. setupBlendShapes(render_scene, pScene, nameWExt);
  100. }
  101. +void USDImporterImplTinyusdz::animations(
  102. + const tinyusdz::tydra::RenderScene& render_scene,
  103. + aiScene* pScene) {
  104. + if (render_scene.animations.empty()) {
  105. + return;
  106. + }
  107. +
  108. + pScene->mNumAnimations = render_scene.animations.size();
  109. + pScene->mAnimations = new aiAnimation *[pScene->mNumAnimations];
  110. +
  111. + for (int animationIndex = 0; animationIndex < pScene->mNumAnimations; ++animationIndex) {
  112. +
  113. + const auto &animation = render_scene.animations[animationIndex];
  114. +
  115. + auto newAiAnimation = new aiAnimation();
  116. + pScene->mAnimations[animationIndex] = newAiAnimation;
  117. +
  118. + newAiAnimation->mName = animation.abs_path;
  119. +
  120. + if (animation.channels_map.empty()) {
  121. + newAiAnimation->mNumChannels = 0;
  122. + continue;
  123. + }
  124. +
  125. + // each channel affects a node (joint)
  126. + newAiAnimation->mTicksPerSecond = render_scene.meta.framesPerSecond;
  127. + newAiAnimation->mNumChannels = animation.channels_map.size();
  128. + newAiAnimation->mChannels = new aiNodeAnim *[newAiAnimation->mNumChannels];
  129. + int channelIndex = 0;
  130. + for (const auto &[jointName, animationChannelMap] : animation.channels_map) {
  131. + auto newAiNodeAnim = new aiNodeAnim();
  132. + newAiAnimation->mChannels[channelIndex] = newAiNodeAnim;
  133. + newAiNodeAnim->mNodeName = jointName;
  134. + newAiAnimation->mDuration = 0;
  135. +
  136. + std::vector<aiVectorKey> positionKeys;
  137. + std::vector<aiQuatKey> rotationKeys;
  138. + std::vector<aiVectorKey> scalingKeys;
  139. +
  140. + for (const auto &[channelType, animChannel] : animationChannelMap) {
  141. + switch (channelType) {
  142. + case tinyusdz::tydra::AnimationChannel::ChannelType::Rotation:
  143. + if (animChannel.rotations.static_value.has_value()) {
  144. + rotationKeys.emplace_back(0, tinyUsdzQuatToAiQuat(animChannel.rotations.static_value.value()));
  145. + }
  146. + for (const auto &rotationAnimSampler : animChannel.rotations.samples) {
  147. + if (rotationAnimSampler.t > newAiAnimation->mDuration) {
  148. + newAiAnimation->mDuration = rotationAnimSampler.t;
  149. + }
  150. +
  151. + rotationKeys.emplace_back(rotationAnimSampler.t, tinyUsdzQuatToAiQuat(rotationAnimSampler.value));
  152. + }
  153. + break;
  154. + case tinyusdz::tydra::AnimationChannel::ChannelType::Scale:
  155. + if (animChannel.scales.static_value.has_value()) {
  156. + scalingKeys.emplace_back(0, tinyUsdzScaleOrPosToAssimp(animChannel.scales.static_value.value()));
  157. + }
  158. + for (const auto &scaleAnimSampler : animChannel.scales.samples) {
  159. + if (scaleAnimSampler.t > newAiAnimation->mDuration) {
  160. + newAiAnimation->mDuration = scaleAnimSampler.t;
  161. + }
  162. + scalingKeys.emplace_back(scaleAnimSampler.t, tinyUsdzScaleOrPosToAssimp(scaleAnimSampler.value));
  163. + }
  164. + break;
  165. + case tinyusdz::tydra::AnimationChannel::ChannelType::Transform:
  166. + if (animChannel.transforms.static_value.has_value()) {
  167. + aiVector3D position;
  168. + aiVector3D scale;
  169. + aiQuaternion rotation;
  170. + tinyUsdzMat4ToAiMat4(animChannel.transforms.static_value.value().m).Decompose(scale, rotation, position);
  171. +
  172. + positionKeys.emplace_back(0, position);
  173. + scalingKeys.emplace_back(0, scale);
  174. + rotationKeys.emplace_back(0, rotation);
  175. + }
  176. + for (const auto &transformAnimSampler : animChannel.transforms.samples) {
  177. + if (transformAnimSampler.t > newAiAnimation->mDuration) {
  178. + newAiAnimation->mDuration = transformAnimSampler.t;
  179. + }
  180. +
  181. + aiVector3D position;
  182. + aiVector3D scale;
  183. + aiQuaternion rotation;
  184. + tinyUsdzMat4ToAiMat4(transformAnimSampler.value.m).Decompose(scale, rotation, position);
  185. +
  186. + positionKeys.emplace_back(transformAnimSampler.t, position);
  187. + scalingKeys.emplace_back(transformAnimSampler.t, scale);
  188. + rotationKeys.emplace_back(transformAnimSampler.t, rotation);
  189. + }
  190. + break;
  191. + case tinyusdz::tydra::AnimationChannel::ChannelType::Translation:
  192. + if (animChannel.translations.static_value.has_value()) {
  193. + positionKeys.emplace_back(0, tinyUsdzScaleOrPosToAssimp(animChannel.translations.static_value.value()));
  194. + }
  195. + for (const auto &translationAnimSampler : animChannel.translations.samples) {
  196. + if (translationAnimSampler.t > newAiAnimation->mDuration) {
  197. + newAiAnimation->mDuration = translationAnimSampler.t;
  198. + }
  199. +
  200. + positionKeys.emplace_back(translationAnimSampler.t, tinyUsdzScaleOrPosToAssimp(translationAnimSampler.value));
  201. + }
  202. + break;
  203. + default:
  204. + TINYUSDZLOGW(TAG, "Unsupported animation channel type (%s). Please update the USD importer to support this animation channel.", tinyusdzAnimChannelTypeFor(channelType).c_str());
  205. + }
  206. + }
  207. +
  208. + newAiNodeAnim->mNumPositionKeys = positionKeys.size();
  209. + newAiNodeAnim->mPositionKeys = new aiVectorKey[newAiNodeAnim->mNumPositionKeys];
  210. + std::move(positionKeys.begin(), positionKeys.end(), newAiNodeAnim->mPositionKeys);
  211. +
  212. + newAiNodeAnim->mNumRotationKeys = rotationKeys.size();
  213. + newAiNodeAnim->mRotationKeys = new aiQuatKey[newAiNodeAnim->mNumRotationKeys];
  214. + std::move(rotationKeys.begin(), rotationKeys.end(), newAiNodeAnim->mRotationKeys);
  215. +
  216. + newAiNodeAnim->mNumScalingKeys = scalingKeys.size();
  217. + newAiNodeAnim->mScalingKeys = new aiVectorKey[newAiNodeAnim->mNumScalingKeys];
  218. + std::move(scalingKeys.begin(), scalingKeys.end(), newAiNodeAnim->mScalingKeys);
  219. +
  220. + ++channelIndex;
  221. + }
  222. + }
  223. +}
  224. void USDImporterImplTinyusdz::meshes(
  225. const tinyusdz::tydra::RenderScene &render_scene,
  226. @@ -247,8 +391,66 @@ void USDImporterImplTinyusdz::verticesForMesh(
  227. size_t meshIdx,
  228. const std::string &nameWExt) {
  229. UNUSED(nameWExt);
  230. - pScene->mMeshes[meshIdx]->mNumVertices = static_cast<unsigned int>(render_scene.meshes[meshIdx].points.size());
  231. + const auto numVertices = static_cast<unsigned int>(render_scene.meshes[meshIdx].points.size());
  232. + pScene->mMeshes[meshIdx]->mNumVertices = numVertices;
  233. pScene->mMeshes[meshIdx]->mVertices = new aiVector3D[pScene->mMeshes[meshIdx]->mNumVertices];
  234. +
  235. + // Check if this is a skinned mesh
  236. + if (int skeleton_id = render_scene.meshes[meshIdx].skel_id; skeleton_id > -1) {
  237. + // Recursively iterate to collect all the joints in the hierarchy into a flattened array
  238. + std::vector<const tinyusdz::tydra::SkelNode *> skeletonNodes;
  239. + skeletonNodes.push_back(&render_scene.skeletons[skeleton_id].root_node);
  240. + for (int i = 0; i < skeletonNodes.size(); ++i) {
  241. + for (const auto &child : skeletonNodes[i]->children) {
  242. + skeletonNodes.push_back(&child);
  243. + }
  244. + }
  245. +
  246. + // Convert USD skeleton joints to Assimp bones
  247. + const unsigned int numBones = skeletonNodes.size();
  248. + pScene->mMeshes[meshIdx]->mNumBones = numBones;
  249. + pScene->mMeshes[meshIdx]->mBones = new aiBone *[numBones];
  250. +
  251. + for (unsigned int i = 0; i < numBones; ++i) {
  252. + const tinyusdz::tydra::SkelNode *skeletonNode = skeletonNodes[i];
  253. + const int boneIndex = skeletonNode->joint_id;
  254. +
  255. + // Sorted so that Assimp bone ids align with USD joint id
  256. + auto outputBone = new aiBone();
  257. + outputBone->mName = aiString(skeletonNode->joint_name);
  258. + outputBone->mOffsetMatrix = tinyUsdzMat4ToAiMat4(skeletonNode->bind_transform.m).Inverse();
  259. + pScene->mMeshes[meshIdx]->mBones[boneIndex] = outputBone;
  260. + }
  261. +
  262. + // Vertex weights
  263. + std::vector<std::vector<aiVertexWeight>> aiBonesVertexWeights;
  264. + aiBonesVertexWeights.resize(numBones);
  265. +
  266. + const std::vector<int> &jointIndices = render_scene.meshes[meshIdx].joint_and_weights.jointIndices;
  267. + const std::vector<float> &jointWeightIndices = render_scene.meshes[meshIdx].joint_and_weights.jointWeights;
  268. + const int numWeightsPerVertex = render_scene.meshes[meshIdx].joint_and_weights.elementSize;
  269. +
  270. + for (unsigned int vertexIndex = 0; vertexIndex < numVertices; ++vertexIndex) {
  271. + for (int weightIndex = 0; weightIndex < numWeightsPerVertex; ++weightIndex) {
  272. + const unsigned int index = vertexIndex * numWeightsPerVertex + weightIndex;
  273. + const float jointWeight = jointWeightIndices[index];
  274. +
  275. + if (jointWeight > 0) {
  276. + const int jointIndex = jointIndices[index];
  277. + aiBonesVertexWeights[jointIndex].emplace_back(vertexIndex, jointWeight);
  278. + }
  279. + }
  280. + }
  281. +
  282. + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) {
  283. + const unsigned int numWeightsForBone = aiBonesVertexWeights[boneIndex].size();
  284. + pScene->mMeshes[meshIdx]->mBones[boneIndex]->mWeights = new aiVertexWeight[numWeightsForBone];
  285. + pScene->mMeshes[meshIdx]->mBones[boneIndex]->mNumWeights = numWeightsForBone;
  286. +
  287. + std::swap_ranges(aiBonesVertexWeights[boneIndex].begin(), aiBonesVertexWeights[boneIndex].end(), pScene->mMeshes[meshIdx]->mBones[boneIndex]->mWeights);
  288. + }
  289. + } // Skinned mesh end
  290. +
  291. for (size_t j = 0; j < pScene->mMeshes[meshIdx]->mNumVertices; ++j) {
  292. pScene->mMeshes[meshIdx]->mVertices[j].x = render_scene.meshes[meshIdx].points[j][0];
  293. pScene->mMeshes[meshIdx]->mVertices[j].y = render_scene.meshes[meshIdx].points[j][1];
  294. @@ -595,54 +797,25 @@ void USDImporterImplTinyusdz::buffers(
  295. }
  296. }
  297. -void USDImporterImplTinyusdz::setupNodes(
  298. - const tinyusdz::tydra::RenderScene &render_scene,
  299. - aiScene *pScene,
  300. - std::map<size_t, tinyusdz::tydra::Node> &meshNodes,
  301. - const std::string &nameWExt) {
  302. - stringstream ss;
  303. -
  304. - pScene->mRootNode = nodes(render_scene, meshNodes, nameWExt);
  305. - pScene->mRootNode->mNumMeshes = pScene->mNumMeshes;
  306. - pScene->mRootNode->mMeshes = new unsigned int[pScene->mRootNode->mNumMeshes];
  307. - ss.str("");
  308. - ss << "setupNodes(): pScene->mNumMeshes: " << pScene->mNumMeshes;
  309. - if (pScene->mRootNode != nullptr) {
  310. - ss << ", mRootNode->mNumMeshes: " << pScene->mRootNode->mNumMeshes;
  311. - }
  312. - TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
  313. -
  314. - for (unsigned int meshIdx = 0; meshIdx < pScene->mNumMeshes; meshIdx++) {
  315. - pScene->mRootNode->mMeshes[meshIdx] = meshIdx;
  316. - }
  317. -
  318. -}
  319. -
  320. -aiNode *USDImporterImplTinyusdz::nodes(
  321. - const tinyusdz::tydra::RenderScene &render_scene,
  322. - std::map<size_t, tinyusdz::tydra::Node> &meshNodes,
  323. - const std::string &nameWExt) {
  324. - const size_t numNodes{render_scene.nodes.size()};
  325. - (void) numNodes; // Ignore unused variable when -Werror enabled
  326. - stringstream ss;
  327. - ss.str("");
  328. - ss << "nodes(): model" << nameWExt << ", numNodes: " << numNodes;
  329. - TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
  330. - return nodesRecursive(nullptr, render_scene.nodes[0], meshNodes);
  331. -}
  332. -
  333. using Assimp::tinyusdzNodeTypeFor;
  334. using Assimp::tinyUsdzMat4ToAiMat4;
  335. using tinyusdz::tydra::NodeType;
  336. aiNode *USDImporterImplTinyusdz::nodesRecursive(
  337. aiNode *pNodeParent,
  338. const tinyusdz::tydra::Node &node,
  339. - std::map<size_t, tinyusdz::tydra::Node> &meshNodes) {
  340. + const std::vector<tinyusdz::tydra::SkelHierarchy> &skeletons) {
  341. stringstream ss;
  342. aiNode *cNode = new aiNode();
  343. cNode->mParent = pNodeParent;
  344. cNode->mName.Set(node.prim_name);
  345. cNode->mTransformation = tinyUsdzMat4ToAiMat4(node.local_matrix.m);
  346. +
  347. + if (node.nodeType == NodeType::Mesh) {
  348. + cNode->mNumMeshes = 1;
  349. + cNode->mMeshes = new unsigned int[cNode->mNumMeshes];
  350. + cNode->mMeshes[0] = node.id;
  351. + }
  352. +
  353. ss.str("");
  354. ss << "nodesRecursive(): node " << cNode->mName.C_Str() <<
  355. " type: |" << tinyusdzNodeTypeFor(node.nodeType) <<
  356. @@ -651,21 +824,69 @@ aiNode *USDImporterImplTinyusdz::nodesRecursive(
  357. ss << " (parent " << cNode->mParent->mName.C_Str() << ")";
  358. }
  359. ss << " has " << node.children.size() << " children";
  360. - if (node.id > -1) {
  361. + if (node.nodeType == NodeType::Mesh) {
  362. ss << "\n node mesh id: " << node.id << " (node type: " << tinyusdzNodeTypeFor(node.nodeType) << ")";
  363. - meshNodes[node.id] = node;
  364. }
  365. TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
  366. - if (!node.children.empty()) {
  367. - cNode->mNumChildren = static_cast<unsigned int>(node.children.size());
  368. - cNode->mChildren = new aiNode *[cNode->mNumChildren];
  369. +
  370. + unsigned int numChildren = node.children.size();
  371. +
  372. + // Find any tinyusdz skeletons which might begin at this node
  373. + // Add the skeleton bones as child nodes
  374. + const tinyusdz::tydra::SkelNode *skelNode = nullptr;
  375. + for (const auto &skeleton : skeletons) {
  376. + if (skeleton.abs_path == node.abs_path) {
  377. + // Add this skeleton's bones as child nodes
  378. + ++numChildren;
  379. + skelNode = &skeleton.root_node;
  380. + break;
  381. + }
  382. }
  383. - size_t i{0};
  384. - for (const auto &childNode: node.children) {
  385. - cNode->mChildren[i] = nodesRecursive(cNode, childNode, meshNodes);
  386. + cNode->mNumChildren = numChildren;
  387. +
  388. + // Done. No more children.
  389. + if (numChildren == 0) {
  390. + return cNode;
  391. + }
  392. +
  393. + cNode->mChildren = new aiNode *[cNode->mNumChildren];
  394. +
  395. + size_t i{ 0 };
  396. + for (const auto &childNode : node.children) {
  397. + cNode->mChildren[i] = nodesRecursive(cNode, childNode, skeletons);
  398. ++i;
  399. }
  400. +
  401. + if (skelNode != nullptr) {
  402. + // Convert USD skeleton into an Assimp node and make it the last child
  403. + cNode->mChildren[cNode->mNumChildren-1] = skeletonNodesRecursive(cNode, *skelNode);
  404. + }
  405. +
  406. + return cNode;
  407. +}
  408. +
  409. +aiNode *USDImporterImplTinyusdz::skeletonNodesRecursive(
  410. + aiNode* pNodeParent,
  411. + const tinyusdz::tydra::SkelNode& joint) {
  412. + auto *cNode = new aiNode(joint.joint_path);
  413. + cNode->mParent = pNodeParent;
  414. + cNode->mNumMeshes = 0; // not a mesh node
  415. + cNode->mTransformation = tinyUsdzMat4ToAiMat4(joint.rest_transform.m);
  416. +
  417. + // Done. No more children.
  418. + if (joint.children.empty()) {
  419. + return cNode;
  420. + }
  421. +
  422. + cNode->mNumChildren = static_cast<unsigned int>(joint.children.size());
  423. + cNode->mChildren = new aiNode *[cNode->mNumChildren];
  424. +
  425. + for (int i = 0; i < cNode->mNumChildren; ++i) {
  426. + const tinyusdz::tydra::SkelNode &childJoint = joint.children[i];
  427. + cNode->mChildren[i] = skeletonNodesRecursive(cNode, childJoint);
  428. + }
  429. +
  430. return cNode;
  431. }
  432. diff --git a/code/AssetLib/USD/USDLoaderImplTinyusdz.h b/code/AssetLib/USD/USDLoaderImplTinyusdz.h
  433. index 69f8c125c..8d52cc383 100644
  434. --- a/code/AssetLib/USD/USDLoaderImplTinyusdz.h
  435. +++ b/code/AssetLib/USD/USDLoaderImplTinyusdz.h
  436. @@ -65,6 +65,10 @@ public:
  437. aiScene *pScene,
  438. IOSystem *pIOHandler);
  439. + void animations(
  440. + const tinyusdz::tydra::RenderScene &render_scene,
  441. + aiScene *pScene);
  442. +
  443. void meshes(
  444. const tinyusdz::tydra::RenderScene &render_scene,
  445. aiScene *pScene,
  446. @@ -120,22 +124,14 @@ public:
  447. aiScene *pScene,
  448. const std::string &nameWExt);
  449. - void setupNodes(
  450. - const tinyusdz::tydra::RenderScene &render_scene,
  451. - aiScene *pScene,
  452. - std::map<size_t, tinyusdz::tydra::Node> &meshNodes,
  453. - const std::string &nameWExt
  454. - );
  455. -
  456. - aiNode *nodes(
  457. - const tinyusdz::tydra::RenderScene &render_scene,
  458. - std::map<size_t, tinyusdz::tydra::Node> &meshNodes,
  459. - const std::string &nameWExt);
  460. -
  461. aiNode *nodesRecursive(
  462. aiNode *pNodeParent,
  463. const tinyusdz::tydra::Node &node,
  464. - std::map<size_t, tinyusdz::tydra::Node> &meshNodes);
  465. + const std::vector<tinyusdz::tydra::SkelHierarchy> &skeletons);
  466. +
  467. + aiNode *skeletonNodesRecursive(
  468. + aiNode *pNodeParent,
  469. + const tinyusdz::tydra::SkelNode &joint);
  470. void sanityCheckNodesRecursive(
  471. aiNode *pNode);
  472. diff --git a/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp b/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp
  473. index 09d692445..6708d7972 100644
  474. --- a/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp
  475. +++ b/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp
  476. @@ -100,43 +100,6 @@ std::string Assimp::tinyusdzNodeTypeFor(NodeType type) {
  477. }
  478. }
  479. -aiMatrix4x4 Assimp::tinyUsdzMat4ToAiMat4(const double matIn[4][4]) {
  480. - aiMatrix4x4 matOut;
  481. - matOut.a1 = matIn[0][0];
  482. - matOut.a2 = matIn[0][1];
  483. - matOut.a3 = matIn[0][2];
  484. - matOut.a4 = matIn[0][3];
  485. - matOut.b1 = matIn[1][0];
  486. - matOut.b2 = matIn[1][1];
  487. - matOut.b3 = matIn[1][2];
  488. - matOut.b4 = matIn[1][3];
  489. - matOut.c1 = matIn[2][0];
  490. - matOut.c2 = matIn[2][1];
  491. - matOut.c3 = matIn[2][2];
  492. - matOut.c4 = matIn[2][3];
  493. - matOut.d1 = matIn[3][0];
  494. - matOut.d2 = matIn[3][1];
  495. - matOut.d3 = matIn[3][2];
  496. - matOut.d4 = matIn[3][3];
  497. -// matOut.a1 = matIn[0][0];
  498. -// matOut.a2 = matIn[1][0];
  499. -// matOut.a3 = matIn[2][0];
  500. -// matOut.a4 = matIn[3][0];
  501. -// matOut.b1 = matIn[0][1];
  502. -// matOut.b2 = matIn[1][1];
  503. -// matOut.b3 = matIn[2][1];
  504. -// matOut.b4 = matIn[3][1];
  505. -// matOut.c1 = matIn[0][2];
  506. -// matOut.c2 = matIn[1][2];
  507. -// matOut.c3 = matIn[2][2];
  508. -// matOut.c4 = matIn[3][2];
  509. -// matOut.d1 = matIn[0][3];
  510. -// matOut.d2 = matIn[1][3];
  511. -// matOut.d3 = matIn[2][3];
  512. -// matOut.d4 = matIn[3][3];
  513. - return matOut;
  514. -}
  515. -
  516. aiVector3D Assimp::tinyUsdzScaleOrPosToAssimp(const std::array<float, 3> &scaleOrPosIn) {
  517. return aiVector3D(scaleOrPosIn[0], scaleOrPosIn[1], scaleOrPosIn[2]);
  518. }
  519. diff --git a/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h b/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h
  520. index c5eaafd73..42a7b9d9f 100644
  521. --- a/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h
  522. +++ b/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h
  523. @@ -48,14 +48,36 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  524. #include <assimp/types.h>
  525. #include "tinyusdz.hh"
  526. #include "tydra/render-data.hh"
  527. +#include <type_traits>
  528. namespace Assimp {
  529. std::string tinyusdzAnimChannelTypeFor(
  530. tinyusdz::tydra::AnimationChannel::ChannelType animChannel);
  531. std::string tinyusdzNodeTypeFor(tinyusdz::tydra::NodeType type);
  532. -aiMatrix4x4 tinyUsdzMat4ToAiMat4(const double matIn[4][4]);
  533. +template <typename T>
  534. +aiMatrix4x4 tinyUsdzMat4ToAiMat4(const T matIn[4][4]) {
  535. + static_assert(std::is_floating_point_v<T>, "Only floating-point types are allowed.");
  536. + aiMatrix4x4 matOut;
  537. + matOut.a1 = matIn[0][0];
  538. + matOut.a2 = matIn[1][0];
  539. + matOut.a3 = matIn[2][0];
  540. + matOut.a4 = matIn[3][0];
  541. + matOut.b1 = matIn[0][1];
  542. + matOut.b2 = matIn[1][1];
  543. + matOut.b3 = matIn[2][1];
  544. + matOut.b4 = matIn[3][1];
  545. + matOut.c1 = matIn[0][2];
  546. + matOut.c2 = matIn[1][2];
  547. + matOut.c3 = matIn[2][2];
  548. + matOut.c4 = matIn[3][2];
  549. + matOut.d1 = matIn[0][3];
  550. + matOut.d2 = matIn[1][3];
  551. + matOut.d3 = matIn[2][3];
  552. + matOut.d4 = matIn[3][3];
  553. + return matOut;
  554. +}
  555. aiVector3D tinyUsdzScaleOrPosToAssimp(const std::array<float, 3> &scaleOrPosIn);
  556. /**
  557. diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt
  558. index 9b2708623..fc6f682ed 100644
  559. --- a/code/CMakeLists.txt
  560. +++ b/code/CMakeLists.txt
  561. @@ -946,8 +946,8 @@ IF (ASSIMP_BUILD_USD_IMPORTER)
  562. # Note: ALWAYS specify a git commit hash (or tag) instead of a branch name; using a branch
  563. # name can lead to non-deterministic (unpredictable) results since the code is potentially
  564. # in flux
  565. - # "dev" branch, 9 Jul 2024
  566. - set(TINYUSDZ_GIT_TAG "bd2a1edbbf69f352a6c40730114db9918c384848")
  567. + # "dev" branch, 28 Oct 2024
  568. + set(TINYUSDZ_GIT_TAG "36f2aabb256b360365989c01a52f839a57dfe2a6")
  569. message("****")
  570. message("\n\n**** Cloning tinyusdz repo, git tag ${TINYUSDZ_GIT_TAG}\n\n")
  571. diff --git a/code/PostProcessing/ValidateDataStructure.cpp b/code/PostProcessing/ValidateDataStructure.cpp
  572. index 8441b48be..fba81a399 100644
  573. --- a/code/PostProcessing/ValidateDataStructure.cpp
  574. +++ b/code/PostProcessing/ValidateDataStructure.cpp
  575. @@ -447,7 +447,7 @@ void ValidateDSProcess::Validate(const aiMesh *pMesh, const aiBone *pBone, float
  576. if (pBone->mWeights[i].mVertexId >= pMesh->mNumVertices) {
  577. ReportError("aiBone::mWeights[%i].mVertexId is out of range", i);
  578. } else if (!pBone->mWeights[i].mWeight || pBone->mWeights[i].mWeight > 1.0f) {
  579. - ReportWarning("aiBone::mWeights[%i].mWeight has an invalid value", i);
  580. + ReportWarning("aiBone::mWeights[%i].mWeight has an invalid value %i. Value must be greater than zero and less than 1.", i, pBone->mWeights[i].mWeight);
  581. }
  582. afSum[pBone->mWeights[i].mVertexId] += pBone->mWeights[i].mWeight;
  583. }
  584. diff --git a/contrib/tinyusdz/patches/tinyusdz.patch b/contrib/tinyusdz/patches/tinyusdz.patch
  585. index e84e9f8fe..7cd703475 100644
  586. --- a/contrib/tinyusdz/patches/tinyusdz.patch
  587. +++ b/contrib/tinyusdz/patches/tinyusdz.patch
  588. @@ -1,7 +1,33 @@
  589. +diff -rupN -x .git autoclone/tinyusdz_repo-src/src/io-util.cc tinyusdz_repo_patch/src/io-util.cc
  590. +--- autoclone/tinyusdz_repo-src/src/io-util.cc 2024-10-27 03:26:45.457163600 -0700
  591. ++++ tinyusdz_repo_patch/src/io-util.cc 2024-10-27 03:31:09.255211100 -0700
  592. +@@ -19,6 +19,7 @@
  593. +
  594. + #include <io.h>
  595. + #include <windows.h> // include API for expanding a file path
  596. ++#include <tchar.h>
  597. +
  598. + #ifndef TINYUSDZ_MMAP_SUPPORTED
  599. + #define TINYUSDZ_MMAP_SUPPORTED (1)
  600. +@@ -153,9 +154,10 @@ bool MMapFile(const std::string &filepath, MMapFileHandle *handle,
  601. +
  602. + #if TINYUSDZ_MMAP_SUPPORTED
  603. + #if defined(_WIN32)
  604. +- // int fd = open(filepath.c_str(), writable ? O_RDWR : O_RDONLY);
  605. ++ std::basic_string<TCHAR> tFilepath(filepath.begin(), filepath.end()); // Using TCHAR string to automatically use normal or wide characters if UNICODE is enabled
  606. ++
  607. + HANDLE hFile =
  608. +- CreateFile(filepath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
  609. ++ CreateFile(tFilepath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
  610. + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
  611. + if (hFile == INVALID_HANDLE_VALUE) {
  612. + if (err) {
  613. +
  614. +
  615. diff -rupN -x .git autoclone/tinyusdz_repo-src/src/external/stb_image_resize2.h tinyusdz_repo_patch/src/external/stb_image_resize2.h
  616. ---- autoclone/tinyusdz_repo-src/src/external/stb_image_resize2.h 2024-07-09 21:29:48.556969900 -0700
  617. -+++ tinyusdz_repo_patch/src/external/stb_image_resize2.h 2024-07-09 23:03:47.379316700 -0700
  618. -@@ -2404,6 +2404,38 @@ static stbir__inline stbir_uint8 stbir__
  619. +--- autoclone/tinyusdz_repo-src/src/external/stb_image_resize2.h 2024-10-27 03:26:45.457163600 -0700
  620. ++++ tinyusdz_repo_patch/src/external/stb_image_resize2.h 2024-10-27 03:31:09.255211100 -0700
  621. +@@ -2500,6 +2500,38 @@ static stbir__inline stbir_uint8 stbir__
  622. }
  623. }
  624. @@ -37,6 +63,6 @@ diff -rupN -x .git autoclone/tinyusdz_repo-src/src/external/stb_image_resize2.h
  625. + return 0;
  626. + }
  627. +
  628. - #elif defined(STBIR_NEON) && defined(_MSC_VER) && defined(_M_ARM64) && !defined(__clang__) // 64-bit ARM on MSVC (not clang)
  629. + #endif
  630. +
  631. - static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input)
  632. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
  633. index 7b7fd850a..fe80edcb3 100644
  634. --- a/test/CMakeLists.txt
  635. +++ b/test/CMakeLists.txt
  636. @@ -148,7 +148,6 @@ SET( IMPORTERS
  637. #unit/utM3DImportExport.cpp
  638. unit/utMDCImportExport.cpp
  639. unit/utAssbinImportExport.cpp
  640. - unit/utUSDImport.cpp
  641. unit/ImportExport/utAssjsonImportExport.cpp
  642. unit/ImportExport/utCOBImportExport.cpp
  643. unit/ImportExport/utOgreImportExport.cpp
  644. @@ -169,6 +168,12 @@ SET( IMPORTERS
  645. unit/ImportExport/Pbrt/utPbrtImportExport.cpp
  646. )
  647. +if(ASSIMP_BUILD_USD_IMPORTER)
  648. + list( APPEND IMPORTERS
  649. + unit/utUSDImport.cpp
  650. + )
  651. +endif()
  652. +
  653. SET( MATERIAL
  654. unit/utMaterialSystem.cpp
  655. )
  656. diff --git a/test/models-nonbsd/USD/usda/README.md b/test/models-nonbsd/USD/usda/README.md
  657. index e860175fd..cb5477b26 100644
  658. --- a/test/models-nonbsd/USD/usda/README.md
  659. +++ b/test/models-nonbsd/USD/usda/README.md
  660. @@ -1,3 +1,5 @@
  661. [blendshape.usda](blendshape.usda) copied from tinyusdz/models (No attribution/license cited in that project)
  662. [texturedcube.usda](texturedcube.usda) copied from tinyusdz/models (No attribution/license cited in that project)
  663. [translated-cube.usda](translated-cube.usda) copied from tinyusdz/models (No attribution/license cited in that project)
  664. +[simple-skin-test.usda](simple-skin-test.usda) copied from tinyusdz/models (No attribution/license cited in that project)
  665. +[simple-skin-animation-test.usda](simple-skin-animation-test.usda) modified tinyusdz/models (No attribution/license cited in that project)
  666. diff --git a/test/models-nonbsd/USD/usda/simple-skin-animation-test.usda b/test/models-nonbsd/USD/usda/simple-skin-animation-test.usda
  667. new file mode 100644
  668. index 000000000..2324c2064
  669. --- /dev/null
  670. +++ b/test/models-nonbsd/USD/usda/simple-skin-animation-test.usda
  671. @@ -0,0 +1,237 @@
  672. +#usda 1.0
  673. +(
  674. + defaultPrim = "root"
  675. + doc = "Blender v4.2.3 LTS"
  676. + endTimeCode = 40
  677. + metersPerUnit = 1
  678. + startTimeCode = 0
  679. + timeCodesPerSecond = 24
  680. + upAxis = "Z"
  681. +)
  682. +
  683. +def Xform "root" (
  684. + customData = {
  685. + dictionary Blender = {
  686. + bool generated = 1
  687. + }
  688. + }
  689. +)
  690. +{
  691. + def SkelRoot "Armature_001"
  692. + {
  693. + custom string userProperties:blender:object_name = "Armature.001"
  694. + float3 xformOp:rotateXYZ = (-89.99999, 0, 0)
  695. + float3 xformOp:scale = (1, 1, 1)
  696. + double3 xformOp:translate = (0, -1.7017418146133423, 0)
  697. + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
  698. +
  699. + def Xform "Grid"
  700. + {
  701. + custom string userProperties:blender:object_name = "Grid"
  702. + float3 xformOp:rotateXYZ = (89.99999, -0, 0)
  703. + float3 xformOp:scale = (1, 1, 1)
  704. + double3 xformOp:translate = (0, -7.438549687321938e-8, 1.7017418146133423)
  705. + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
  706. +
  707. + def Mesh "Grid" (
  708. + active = true
  709. + prepend apiSchemas = ["SkelBindingAPI"]
  710. + )
  711. + {
  712. + float3[] extent = [(-1, -1, 0), (1, 1, 0)]
  713. + int[] faceVertexCounts = [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
  714. + int[] faceVertexIndices = [0, 1, 6, 5, 1, 2, 7, 6, 2, 3, 8, 7, 3, 4, 9, 8, 5, 6, 11, 10, 6, 7, 12, 11, 7, 8, 13, 12, 8, 9, 14, 13, 10, 11, 16, 15, 11, 12, 17, 16, 12, 13, 18, 17, 13, 14, 19, 18, 15, 16, 21, 20, 16, 17, 22, 21, 17, 18, 23, 22, 18, 19, 24, 23]
  715. + normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1)] (
  716. + interpolation = "faceVarying"
  717. + )
  718. + point3f[] points = [(-1, -1, 0), (-0.5, -1, 0), (0, -1, 0), (0.5, -1, 0), (1, -1, 0), (-1, -0.5, 0), (-0.5, -0.5, 0), (0, -0.5, 0), (0.5, -0.5, 0), (1, -0.5, 0), (-1, 0, 0), (-0.5, 0, 0), (0, 0, 0), (0.5, 0, 0), (1, 0, 0), (-1, 0.5, 0), (-0.5, 0.5, 0), (0, 0.5, 0), (0.5, 0.5, 0), (1, 0.5, 0), (-1, 1, 0), (-0.5, 1, 0), (0, 1, 0), (0.5, 1, 0), (1, 1, 0)]
  719. + bool[] primvars:sharp_face = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] (
  720. + interpolation = "uniform"
  721. + )
  722. + matrix4d primvars:skel:geomBindTransform = ( (1, 0, 0, 0), (0, 1.331580543606492e-7, 0.9999999999999911, 0), (0, -0.9999999999999911, 1.331580543606492e-7, 0), (0, -7.438549687321943e-8, 1.701741814613342, 1) )
  723. + int[] primvars:skel:jointIndices = [0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1] (
  724. + elementSize = 2
  725. + interpolation = "vertex"
  726. + )
  727. + float[] primvars:skel:jointWeights = [0.43767902, 0.56232095, 0.605441, 0.39455906, 1, 0, 0.605441, 0.39455906, 0.43767902, 0.562321, 0.23470172, 0.7652983, 0.18096954, 0.81903046, 1, 0, 0.18096954, 0.81903046, 0.23470171, 0.7652983, 0.114853, 0.88514704, 0.06506716, 0.9349329, 1, 0, 0.06506715, 0.9349329, 0.11485299, 0.88514704, 0.059175473, 0.94082457, 0.009479873, 0.9905201, 1, 0, 0.009479865, 0.9905201, 0.059175465, 0.94082457, 0.031181445, 0.96881855, 1, 0, 1, 0, 1, 0, 0.031181442, 0.9688186] (
  728. + elementSize = 2
  729. + interpolation = "vertex"
  730. + )
  731. + texCoord2f[] primvars:st = [(0, 0), (0.25, 0), (0.25, 0.25), (0, 0.25), (0.25, 0), (0.5, 0), (0.5, 0.25), (0.25, 0.25), (0.5, 0), (0.75, 0), (0.75, 0.25), (0.5, 0.25), (0.75, 0), (1, 0), (1, 0.25), (0.75, 0.25), (0, 0.25), (0.25, 0.25), (0.25, 0.5), (0, 0.5), (0.25, 0.25), (0.5, 0.25), (0.5, 0.5), (0.25, 0.5), (0.5, 0.25), (0.75, 0.25), (0.75, 0.5), (0.5, 0.5), (0.75, 0.25), (1, 0.25), (1, 0.5), (0.75, 0.5), (0, 0.5), (0.25, 0.5), (0.25, 0.75), (0, 0.75), (0.25, 0.5), (0.5, 0.5), (0.5, 0.75), (0.25, 0.75), (0.5, 0.5), (0.75, 0.5), (0.75, 0.75), (0.5, 0.75), (0.75, 0.5), (1, 0.5), (1, 0.75), (0.75, 0.75), (0, 0.75), (0.25, 0.75), (0.25, 1), (0, 1), (0.25, 0.75), (0.5, 0.75), (0.5, 1), (0.25, 1), (0.5, 0.75), (0.75, 0.75), (0.75, 1), (0.5, 1), (0.75, 0.75), (1, 0.75), (1, 1), (0.75, 1)] (
  732. + interpolation = "faceVarying"
  733. + )
  734. + rel skel:skeleton = </root/Armature_001/Armature/Armature>
  735. + uniform token subdivisionScheme = "none"
  736. + custom string userProperties:blender:data_name = "Grid"
  737. + }
  738. + }
  739. +
  740. + def Xform "Armature"
  741. + {
  742. + custom string userProperties:blender:object_name = "Armature"
  743. + float3 xformOp:rotateXYZ.timeSamples = {
  744. + 0: (0, -0, 0),
  745. + }
  746. + float3 xformOp:scale.timeSamples = {
  747. + 0: (1, 1, 1),
  748. + }
  749. + double3 xformOp:translate.timeSamples = {
  750. + 0: (0, 0, 0),
  751. + }
  752. + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
  753. +
  754. + def Skeleton "Armature" (
  755. + prepend apiSchemas = ["SkelBindingAPI"]
  756. + )
  757. + {
  758. + uniform matrix4d[] bindTransforms = [( (1, 0, 0, 0), (0, 0, 1, 0), (0, -1, 0, 0), (0, 0, 0, 1) ), ( (1, 0, 0, 0), (0, 0, 1, 0), (0, -1, 0, 0), (0, 0, 1, 1) )]
  759. + uniform token[] joints = ["Bone", "Bone/Bone_001"]
  760. + uniform matrix4d[] restTransforms = [( (1, 0, 0, 0), (0, 0, 1, 0), (0, -1, 0, 0), (0, 0, 0, 1) ), ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1, 0, 1) )]
  761. + rel skel:animationSource = </root/Armature_001/Armature/Armature/Anim>
  762. +
  763. + def SkelAnimation "Anim"
  764. + {
  765. + uniform token[] joints = ["Bone", "Bone/Bone_001"]
  766. + quatf[] rotations.timeSamples = {
  767. + 0: [(0.70710677, 0.70710677, 0, 0), (1, 0, 0, 0)],
  768. + 1: [(0.70710677, 0.70710677, 0, 0), (0.9996082, 0, 0, 0.027989032)],
  769. + 2: [(0.70710677, 0.70710677, 0, 0), (0.99463546, 0, 0, 0.10344209)],
  770. + 3: [(0.70710677, 0.70710677, 0, 0), (0.9774578, 0, 0, 0.21113087)],
  771. + 4: [(0.70710677, 0.70710677, 0, 0), (0.9432686, 0, 0, 0.3320306)],
  772. + 5: [(0.70710677, 0.70710677, 0, 0), (0.89442724, 0, 0, 0.4472135)],
  773. + 6: [(0.70710677, 0.70710677, 0, 0), (0.83920974, 0, 0, 0.5438079)],
  774. + 7: [(0.70710677, 0.70710677, 0, 0), (0.7869733, 0, 0, 0.61698705)],
  775. + 8: [(0.70710677, 0.70710677, 0, 0), (0.7447736, 0, 0, 0.6673172)],
  776. + 9: [(0.70710677, 0.70710677, 0, 0), (0.7170745, 0, 0, 0.6969965)],
  777. + 10: [(0.70710677, 0.70710677, 0, 0), (0.70710677, 0, 0, 0.70710677)],
  778. + 11: [(0.70710677, 0.70710677, 0, 0), (0.7122517, 0, 0, 0.70192415)],
  779. + 12: [(0.70710677, 0.70710677, 0, 0), (0.7271744, 0, 0, 0.68645275)],
  780. + 13: [(0.70710677, 0.70710677, 0, 0), (0.75127214, 0, 0, 0.6599926)],
  781. + 14: [(0.70710677, 0.70710677, 0, 0), (0.7839187, 0, 0, 0.62086356)],
  782. + 15: [(0.70710677, 0.70710677, 0, 0), (0.82404196, 0, 0, 0.5665288)],
  783. + 16: [(0.70710677, 0.70710677, 0, 0), (0.8695245, 0, 0, 0.49388987)],
  784. + 17: [(0.70710677, 0.70710677, 0, 0), (0.91649354, 0, 0, 0.4000495)],
  785. + 18: [(0.70710677, 0.70710677, 0, 0), (0.9588755, 0, 0, 0.28382713)],
  786. + 19: [(0.70710677, 0.70710677, 0, 0), (0.9890088, 0, 0, 0.14785683)],
  787. + 20: [(0.70710677, 0.70710677, 0, 0), (1, 0, 0, 0)],
  788. + 21: [(0.70710677, 0.70710677, 0, 0), (0.9890088, 0, 0, -0.14785682)],
  789. + 22: [(0.70710677, 0.70710677, 0, 0), (0.9588755, 0, 0, -0.28382716)],
  790. + 23: [(0.70710677, 0.70710677, 0, 0), (0.91649354, 0, 0, -0.40004945)],
  791. + 24: [(0.70710677, 0.70710677, 0, 0), (0.86952454, 0, 0, -0.49388978)],
  792. + 25: [(0.70710677, 0.70710677, 0, 0), (0.82404196, 0, 0, -0.5665288)],
  793. + 26: [(0.70710677, 0.70710677, 0, 0), (0.7839186, 0, 0, -0.6208636)],
  794. + 27: [(0.70710677, 0.70710677, 0, 0), (0.7512721, 0, 0, -0.65999264)],
  795. + 28: [(0.70710677, 0.70710677, 0, 0), (0.7271744, 0, 0, -0.68645275)],
  796. + 29: [(0.70710677, 0.70710677, 0, 0), (0.7122517, 0, 0, -0.70192415)],
  797. + 30: [(0.70710677, 0.70710677, 0, 0), (0.70710677, 0, 0, -0.70710677)],
  798. + 31: [(0.70710677, 0.70710677, 0, 0), (0.7170746, 0, 0, -0.69699645)],
  799. + 32: [(0.70710677, 0.70710677, 0, 0), (0.7447736, 0, 0, -0.6673172)],
  800. + 33: [(0.70710677, 0.70710677, 0, 0), (0.7869733, 0, 0, -0.61698705)],
  801. + 34: [(0.70710677, 0.70710677, 0, 0), (0.83920974, 0, 0, -0.5438079)],
  802. + 35: [(0.70710677, 0.70710677, 0, 0), (0.8944272, 0, 0, -0.44721356)],
  803. + 36: [(0.70710677, 0.70710677, 0, 0), (0.9432686, 0, 0, -0.3320306)],
  804. + 37: [(0.70710677, 0.70710677, 0, 0), (0.97745776, 0, 0, -0.21113095)],
  805. + 38: [(0.70710677, 0.70710677, 0, 0), (0.99463546, 0, 0, -0.10344207)],
  806. + 39: [(0.70710677, 0.70710677, 0, 0), (0.9996082, 0, 0, -0.027989028)],
  807. + 40: [(0.70710677, 0.70710677, 0, 0), (1, 0, 0, 0)],
  808. + }
  809. + half3[] scales.timeSamples = {
  810. + 0: [(1, 1, 1), (1, 1, 1)],
  811. + 1: [(1, 1, 1), (1, 1, 1)],
  812. + 2: [(1, 1, 1), (1, 1, 1)],
  813. + 3: [(1, 1, 1), (1, 1, 1)],
  814. + 4: [(1, 1, 1), (1, 1, 1)],
  815. + 5: [(1, 1, 1), (1, 1, 1)],
  816. + 6: [(1, 1, 1), (1, 1, 1)],
  817. + 7: [(1, 1, 1), (1, 1, 1)],
  818. + 8: [(1, 1, 1), (1, 1, 1)],
  819. + 9: [(1, 1, 1), (1, 1, 1)],
  820. + 10: [(1, 1, 1), (1, 1, 1)],
  821. + 11: [(1, 1, 1), (1, 1, 1)],
  822. + 12: [(1, 1, 1), (1, 1, 1)],
  823. + 13: [(1, 1, 1), (1, 1, 1)],
  824. + 14: [(1, 1, 1), (1, 1, 1)],
  825. + 15: [(1, 1, 1), (1, 1, 1)],
  826. + 16: [(1, 1, 1), (1, 1, 1)],
  827. + 17: [(1, 1, 1), (1, 1, 1)],
  828. + 18: [(1, 1, 1), (1, 1, 1)],
  829. + 19: [(1, 1, 1), (1, 1, 1)],
  830. + 20: [(1, 1, 1), (1, 1, 1)],
  831. + 21: [(1, 1, 1), (1, 1, 1)],
  832. + 22: [(1, 1, 1), (1, 1, 1)],
  833. + 23: [(1, 1, 1), (1, 1, 1)],
  834. + 24: [(1, 1, 1), (1, 1, 1)],
  835. + 25: [(1, 1, 1), (1, 1, 1)],
  836. + 26: [(1, 1, 1), (1, 1, 1)],
  837. + 27: [(1, 1, 1), (1, 1, 1)],
  838. + 28: [(1, 1, 1), (1, 1, 1)],
  839. + 29: [(1, 1, 1), (1, 1, 1)],
  840. + 30: [(1, 1, 1), (1, 1, 1)],
  841. + 31: [(1, 1, 1), (1, 1, 1)],
  842. + 32: [(1, 1, 1), (1, 1, 1)],
  843. + 33: [(1, 1, 1), (1, 1, 1)],
  844. + 34: [(1, 1, 1), (1, 1, 1)],
  845. + 35: [(1, 1, 1), (1, 1, 1)],
  846. + 36: [(1, 1, 1), (1, 1, 1)],
  847. + 37: [(1, 1, 1), (1, 1, 1)],
  848. + 38: [(1, 1, 1), (1, 1, 1)],
  849. + 39: [(1, 1, 1), (1, 1, 1)],
  850. + 40: [(1, 1, 1), (1, 1, 1)],
  851. + }
  852. + float3[] translations.timeSamples = {
  853. + 0: [(0, 0, 0), (0, 1, 0)],
  854. + 1: [(0, 0, 0), (0, 1, 0)],
  855. + 2: [(0, 0, 0), (0, 1, 0)],
  856. + 3: [(0, 0, 0), (0, 1, 0)],
  857. + 4: [(0, 0, 0), (0, 1, 0)],
  858. + 5: [(0, 0, 0), (0, 1, 0)],
  859. + 6: [(0, 0, 0), (0, 1, 0)],
  860. + 7: [(0, 0, 0), (0, 1, 0)],
  861. + 8: [(0, 0, 0), (0, 1, 0)],
  862. + 9: [(0, 0, 0), (0, 1, 0)],
  863. + 10: [(0, 0, 0), (0, 1, 0)],
  864. + 11: [(0, 0, 0), (0, 1, 0)],
  865. + 12: [(0, 0, 0), (0, 1, 0)],
  866. + 13: [(0, 0, 0), (0, 1, 0)],
  867. + 14: [(0, 0, 0), (0, 1, 0)],
  868. + 15: [(0, 0, 0), (0, 1, 0)],
  869. + 16: [(0, 0, 0), (0, 1, 0)],
  870. + 17: [(0, 0, 0), (0, 1, 0)],
  871. + 18: [(0, 0, 0), (0, 1, 0)],
  872. + 19: [(0, 0, 0), (0, 1, 0)],
  873. + 20: [(0, 0, 0), (0, 1, 0)],
  874. + 21: [(0, 0, 0), (0, 1, 0)],
  875. + 22: [(0, 0, 0), (0, 1, 0)],
  876. + 23: [(0, 0, 0), (0, 1, 0)],
  877. + 24: [(0, 0, 0), (0, 1, 0)],
  878. + 25: [(0, 0, 0), (0, 1, 0)],
  879. + 26: [(0, 0, 0), (0, 1, 0)],
  880. + 27: [(0, 0, 0), (0, 1, 0)],
  881. + 28: [(0, 0, 0), (0, 1, 0)],
  882. + 29: [(0, 0, 0), (0, 1, 0)],
  883. + 30: [(0, 0, 0), (0, 1, 0)],
  884. + 31: [(0, 0, 0), (0, 1, 0)],
  885. + 32: [(0, 0, 0), (0, 1, 0)],
  886. + 33: [(0, 0, 0), (0, 1, 0)],
  887. + 34: [(0, 0, 0), (0, 1, 0)],
  888. + 35: [(0, 0, 0), (0, 1, 0)],
  889. + 36: [(0, 0, 0), (0, 1, 0)],
  890. + 37: [(0, 0, 0), (0, 1, 0)],
  891. + 38: [(0, 0, 0), (0, 1, 0)],
  892. + 39: [(0, 0, 0), (0, 1, 0)],
  893. + 40: [(0, 0, 0), (0, 1, 0)],
  894. + }
  895. + }
  896. + }
  897. + }
  898. + }
  899. +
  900. + def DomeLight "env_light"
  901. + {
  902. + float inputs:intensity = 1
  903. + asset inputs:texture:file = @.\textures\color_121212.hdr@
  904. + float3 xformOp:rotateXYZ = (90, 1.2722219e-14, 90)
  905. + uniform token[] xformOpOrder = ["xformOp:rotateXYZ"]
  906. + }
  907. +}
  908. +
  909. diff --git a/test/models-nonbsd/USD/usda/simple-skin-test.usda b/test/models-nonbsd/USD/usda/simple-skin-test.usda
  910. new file mode 100644
  911. index 000000000..501b79b72
  912. --- /dev/null
  913. +++ b/test/models-nonbsd/USD/usda/simple-skin-test.usda
  914. @@ -0,0 +1,85 @@
  915. +#usda 1.0
  916. +(
  917. + defaultPrim = "root"
  918. + doc = "Blender v4.1.0"
  919. + metersPerUnit = 1
  920. + upAxis = "Z"
  921. +)
  922. +
  923. +def Xform "root" (
  924. + customData = {
  925. + dictionary Blender = {
  926. + bool generated = 1
  927. + }
  928. + }
  929. +)
  930. +{
  931. + def SkelRoot "Armature"
  932. + {
  933. + matrix4d xformOp:transform = ( (1, 0, 0, 0), (0, -4.371138828673793e-8, -1, 0), (0, 1, -4.371138828673793e-8, 0), (0, -1.7017418146133423, 0, 1) )
  934. + uniform token[] xformOpOrder = ["xformOp:transform"]
  935. +
  936. + def Skeleton "Armature" (
  937. + prepend apiSchemas = ["SkelBindingAPI"]
  938. + )
  939. + {
  940. + uniform matrix4d[] bindTransforms = [( (1, 0, 0, 0), (0, 0, 1, 0), (0, -1, 0, 0), (0, 0, 0, 1) ), ( (1, 0, 0, 0), (0, 0, 1, 0), (0, -1, 0, 0), (0, 0, 1, 1) )]
  941. + uniform token[] joints = ["Bone", "Bone/Bone_001"]
  942. + uniform matrix4d[] restTransforms = [( (1, 0, 0, 0), (0, 0, 1, 0), (0, -1, 0, 0), (0, 0, 0, 1) ), ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1, 0, 1) )]
  943. + }
  944. +
  945. + def Xform "Grid"
  946. + {
  947. + matrix4d xformOp:transform = ( (1, 0, 0, 0), (0, -4.371138828673793e-8, 1, 0), (0, -1, -4.371138828673793e-8, 0), (0, -7.438549687321938e-8, 1.7017418146133423, 1) )
  948. + uniform token[] xformOpOrder = ["xformOp:transform"]
  949. +
  950. + def Mesh "Grid" (
  951. + prepend apiSchemas = ["SkelBindingAPI"]
  952. + )
  953. + {
  954. + float3[] extent = [(-1, -1, 0), (1, 1, 0)]
  955. + int[] faceVertexCounts = [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
  956. + int[] faceVertexIndices = [0, 1, 6, 5, 1, 2, 7, 6, 2, 3, 8, 7, 3, 4, 9, 8, 5, 6, 11, 10, 6, 7, 12, 11, 7, 8, 13, 12, 8, 9, 14, 13, 10, 11, 16, 15, 11, 12, 17, 16, 12, 13, 18, 17, 13, 14, 19, 18, 15, 16, 21, 20, 16, 17, 22, 21, 17, 18, 23, 22, 18, 19, 24, 23]
  957. + normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1)] (
  958. + interpolation = "faceVarying"
  959. + )
  960. + point3f[] points = [(-1, -1, 0), (-0.5, -1, 0), (0, -1, 0), (0.5, -1, 0), (1, -1, 0), (-1, -0.5, 0), (-0.5, -0.5, 0), (0, -0.5, 0), (0.5, -0.5, 0), (1, -0.5, 0), (-1, 0, 0), (-0.5, 0, 0), (0, 0, 0), (0.5, 0, 0), (1, 0, 0), (-1, 0.5, 0), (-0.5, 0.5, 0), (0, 0.5, 0), (0.5, 0.5, 0), (1, 0.5, 0), (-1, 1, 0), (-0.5, 1, 0), (0, 1, 0), (0.5, 1, 0), (1, 1, 0)]
  961. + bool[] primvars:sharp_face = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] (
  962. + interpolation = "uniform"
  963. + )
  964. + matrix4d primvars:skel:geomBindTransform = ( (1, 0, 0, 0), (0, -4.371138828673793e-8, 1, 0), (0, -1, -4.371138828673793e-8, 0), (0, -7.438549687321938e-8, 1.7017418146133423, 1) )
  965. + int[] primvars:skel:jointIndices = [0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1] (
  966. + elementSize = 2
  967. + interpolation = "vertex"
  968. + )
  969. + float[] primvars:skel:jointWeights = [0.43767902, 0.56232095, 0.605441, 0.39455906, 1, 0, 0.605441, 0.39455906, 0.43767902, 0.562321, 0.23470172, 0.7652983, 0.18096954, 0.81903046, 1, 0, 0.18096954, 0.81903046, 0.23470171, 0.7652983, 0.114853, 0.88514704, 0.06506716, 0.9349329, 1, 0, 0.06506715, 0.9349329, 0.11485299, 0.88514704, 0.059175473, 0.94082457, 0.009479873, 0.9905201, 1, 0, 0.009479865, 0.9905201, 0.059175465, 0.94082457, 0.031181445, 0.96881855, 1, 0, 1, 0, 1, 0, 0.031181442, 0.9688186] (
  970. + elementSize = 2
  971. + interpolation = "vertex"
  972. + )
  973. + texCoord2f[] primvars:UVMap = [(0, 0), (0.25, 0), (0.25, 0.25), (0, 0.25), (0.25, 0), (0.5, 0), (0.5, 0.25), (0.25, 0.25), (0.5, 0), (0.75, 0), (0.75, 0.25), (0.5, 0.25), (0.75, 0), (1, 0), (1, 0.25), (0.75, 0.25), (0, 0.25), (0.25, 0.25), (0.25, 0.5), (0, 0.5), (0.25, 0.25), (0.5, 0.25), (0.5, 0.5), (0.25, 0.5), (0.5, 0.25), (0.75, 0.25), (0.75, 0.5), (0.5, 0.5), (0.75, 0.25), (1, 0.25), (1, 0.5), (0.75, 0.5), (0, 0.5), (0.25, 0.5), (0.25, 0.75), (0, 0.75), (0.25, 0.5), (0.5, 0.5), (0.5, 0.75), (0.25, 0.75), (0.5, 0.5), (0.75, 0.5), (0.75, 0.75), (0.5, 0.75), (0.75, 0.5), (1, 0.5), (1, 0.75), (0.75, 0.75), (0, 0.75), (0.25, 0.75), (0.25, 1), (0, 1), (0.25, 0.75), (0.5, 0.75), (0.5, 1), (0.25, 1), (0.5, 0.75), (0.75, 0.75), (0.75, 1), (0.5, 1), (0.75, 0.75), (1, 0.75), (1, 1), (0.75, 1)] (
  974. + interpolation = "faceVarying"
  975. + )
  976. + rel skel:skeleton = </root/Armature/Armature>
  977. + uniform token subdivisionScheme = "none"
  978. + }
  979. + }
  980. + }
  981. +
  982. + def Xform "Camera"
  983. + {
  984. + matrix4d xformOp:transform = ( (0.6859206557273865, 0.7276763319969177, 0, 0), (-0.32401347160339355, 0.305420845746994, 0.8953956365585327, 0), (0.6515582203865051, -0.6141703724861145, 0.44527140259742737, 0), (7.358891487121582, -6.925790786743164, 4.958309173583984, 1) )
  985. + uniform token[] xformOpOrder = ["xformOp:transform"]
  986. +
  987. + def Camera "Camera"
  988. + {
  989. + float2 clippingRange = (0.1, 100)
  990. + float focalLength = 0.5
  991. + float horizontalAperture = 0.36
  992. + float horizontalApertureOffset = 0
  993. + token projection = "perspective"
  994. + float verticalAperture = 0.2025
  995. + float verticalApertureOffset = 0
  996. + }
  997. + }
  998. +}
  999. +
  1000. diff --git a/test/unit/utUSDImport.cpp b/test/unit/utUSDImport.cpp
  1001. index 2f4ffeaf4..c19ef2679 100644
  1002. --- a/test/unit/utUSDImport.cpp
  1003. +++ b/test/unit/utUSDImport.cpp
  1004. @@ -49,18 +49,42 @@ Copyright (c) 2006-2024, assimp team
  1005. using namespace ::Assimp;
  1006. class utUSDImport : public AbstractImportExportBase {
  1007. -public:
  1008. - virtual bool importerTest() {
  1009. - Assimp::Importer importer;
  1010. - const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/../models-nonbsd/USD/suzanne.usdc", aiProcess_ValidateDataStructure);
  1011. - EXPECT_EQ(1u, scene->mNumMeshes);
  1012. - EXPECT_NE(nullptr, scene->mMeshes[0]);
  1013. - if (nullptr == scene->mMeshes[0]) {
  1014. - return false;
  1015. - }
  1016. - EXPECT_EQ(507u, scene->mMeshes[0]->mNumVertices);
  1017. - EXPECT_EQ(968u, scene->mMeshes[0]->mNumFaces);
  1018. -
  1019. - return (nullptr != scene);
  1020. - }
  1021. };
  1022. +
  1023. +TEST_F(utUSDImport, meshTest) {
  1024. + Assimp::Importer importer;
  1025. + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/../models-nonbsd/USD/usdc/suzanne.usdc", aiProcess_ValidateDataStructure);
  1026. + EXPECT_NE(nullptr, scene);
  1027. + EXPECT_EQ(1u, scene->mNumMeshes);
  1028. + EXPECT_NE(nullptr, scene->mMeshes[0]);
  1029. + EXPECT_EQ(1968u, scene->mMeshes[0]->mNumVertices); // Note: suzanne is authored with only 507 vertices, but TinyUSDZ rebuilds the vertex array. see https://github.com/lighttransport/tinyusdz/blob/36f2aabb256b360365989c01a52f839a57dfe2a6/src/tydra/render-data.cc#L2673-L2690
  1030. + EXPECT_EQ(968u, scene->mMeshes[0]->mNumFaces);
  1031. +}
  1032. +
  1033. +TEST_F(utUSDImport, skinnedMeshTest) {
  1034. + Assimp::Importer importer;
  1035. + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/../models-nonbsd/USD/usda/simple-skin-test.usda", aiProcess_ValidateDataStructure);
  1036. + EXPECT_NE(nullptr, scene);
  1037. + EXPECT_TRUE(scene->HasMeshes());
  1038. +
  1039. + const aiMesh *mesh = scene->mMeshes[0];
  1040. + EXPECT_EQ(2, mesh->mNumBones);
  1041. +
  1042. + // Check bone names and make sure scene has nodes of the same name
  1043. + EXPECT_EQ(mesh->mBones[0]->mName, aiString("Bone"));
  1044. + EXPECT_EQ(mesh->mBones[1]->mName, aiString("Bone/Bone_001"));
  1045. +
  1046. + EXPECT_NE(nullptr, scene->mRootNode->FindNode("Bone"));
  1047. + EXPECT_NE(nullptr, scene->mRootNode->FindNode("Bone/Bone_001"));
  1048. +}
  1049. +
  1050. +TEST_F(utUSDImport, singleAnimationTest) {
  1051. + Assimp::Importer importer;
  1052. + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/../models-nonbsd/USD/usda/simple-skin-animation-test.usda", aiProcess_ValidateDataStructure);
  1053. + EXPECT_NE(nullptr, scene);
  1054. + EXPECT_TRUE(scene->HasAnimations());
  1055. + EXPECT_EQ(2, scene->mAnimations[0]->mNumChannels); // 2 bones. 1 channel for each bone
  1056. +}
  1057. +
  1058. +// Note: Add multi-animation test once supported by USD
  1059. +// See https://github.com/lighttransport/tinyusdz/issues/122 for details.