From e51eaf54c83d2af8bd92d59f10a57ea8aed730cc Mon Sep 17 00:00:00 2001 From: Steve M Date: Thu, 2 May 2024 16:14:20 -0700 Subject: [PATCH] Update with blendshape support --- code/AssetLib/USD/USDLoader.cpp | 9 +- code/AssetLib/USD/USDLoaderImplTinyusdz.cpp | 256 ++++-- code/AssetLib/USD/USDLoaderImplTinyusdz.h | 39 +- .../USD/USDLoaderImplTinyusdzHelper.cpp | 110 +++ .../USD/USDLoaderImplTinyusdzHelper.h | 29 + code/CMakeLists.txt | 2 + code/Common/BaseImporter.cpp | 5 +- .../tinyusdz/tinyusdz_repo/src/pprinter.cc | 2 +- .../tinyusdz/tinyusdz_repo/src/prim-types.hh | 2 + .../tinyusdz_repo/src/tydra/obj-export.cc | 225 ++++- .../tinyusdz_repo/src/tydra/render-data.cc | 844 ++++++++++++++---- .../tinyusdz_repo/src/tydra/render-data.hh | 60 +- .../tinyusdz_repo/src/tydra/scene-access.cc | 759 ++++++++++------ .../tinyusdz_repo/src/tydra/scene-access.hh | 40 +- contrib/tinyusdz/tinyusdz_repo/src/usdGeom.cc | 32 +- contrib/tinyusdz/tinyusdz_repo/src/usdSkel.cc | 14 +- include/assimp/BaseImporter.h | 3 +- 17 files changed, 1832 insertions(+), 599 deletions(-) create mode 100644 code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp create mode 100644 code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h diff --git a/code/AssetLib/USD/USDLoader.cpp b/code/AssetLib/USD/USDLoader.cpp index c79b45f0f..135243a89 100644 --- a/code/AssetLib/USD/USDLoader.cpp +++ b/code/AssetLib/USD/USDLoader.cpp @@ -95,10 +95,13 @@ bool USDImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool c } // Based on extension - if (isUsda(pFile) || isUsdc(pFile)) { - return true; + // TODO: confirm OK to replace this w/SimpleExtensionCheck() below + canRead = isUsd(pFile) || isUsda(pFile) || isUsdc(pFile) || isUsdz(pFile); + if (canRead) { + return canRead; } - return true; + canRead = SimpleExtensionCheck(pFile, "usd", "usda", "usdc", "usdz"); + return canRead; } const aiImporterDesc *USDImporter::GetInfo() const { diff --git a/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp b/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp index 0f7c32b11..398940fca 100644 --- a/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp +++ b/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp @@ -50,6 +50,7 @@ Copyright (c) 2006-2024, assimp team // internal headers #include #include +#include #include #include #include @@ -57,20 +58,20 @@ Copyright (c) 2006-2024, assimp team #include #include #include -#include #include #include #include "io-util.hh" // namespace tinyusdz::io #include "tydra/scene-access.hh" #include "tydra/shader-network.hh" +#include "USDLoaderImplTinyusdzHelper.h" #include "USDLoaderImplTinyusdz.h" #include "USDLoaderUtil.h" #include "../../../contrib/tinyusdz/assimp_tinyusdz_logging.inc" namespace { - const char *const TAG = "USDLoaderImplTinyusdz (C++)"; + const char *const TAG = "tinyusdz loader"; } namespace Assimp { @@ -188,23 +189,44 @@ void USDImporterImplTinyusdz::InternReadFile( TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); return; } + +// sanityCheckNodesRecursive(pScene->mRootNode); + meshes(render_scene, pScene, nameWExt); + materials(render_scene, pScene, nameWExt); + textures(render_scene, pScene, nameWExt); + textureImages(render_scene, pScene, nameWExt); + buffers(render_scene, pScene, nameWExt); + + std::map meshNodes; + setupNodes(render_scene, pScene, meshNodes, nameWExt); + + setupBlendShapes(render_scene, pScene, nameWExt); +} + +void USDImporterImplTinyusdz::meshes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + stringstream ss; pScene->mNumMeshes = render_scene.meshes.size(); - ss.str(""); - ss << "InternReadFile(): mNumMeshes: " << pScene->mNumMeshes; - TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); pScene->mMeshes = new aiMesh *[pScene->mNumMeshes](); - // Create root node - pScene->mRootNode = new aiNode(); - pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; ss.str(""); - ss << "InternReadFile(): mRootNode->mNumMeshes: " << pScene->mRootNode->mNumMeshes; - TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); - pScene->mRootNode->mMeshes = new unsigned int[pScene->mRootNode->mNumMeshes]; + ss << "meshes(): pScene->mNumMeshes: " << pScene->mNumMeshes; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); // Export meshes for (size_t meshIdx = 0; meshIdx < pScene->mNumMeshes; meshIdx++) { pScene->mMeshes[meshIdx] = new aiMesh(); pScene->mMeshes[meshIdx]->mName.Set(render_scene.meshes[meshIdx].prim_name); + ss.str(""); + ss << " mesh[" << meshIdx << "]: " << + render_scene.meshes[meshIdx].joint_and_weights.jointIndices.size() << " jointIndices, " << + render_scene.meshes[meshIdx].joint_and_weights.jointWeights.size() << " jointWeights, elementSize: " << + render_scene.meshes[meshIdx].joint_and_weights.elementSize; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + ss.str(""); + ss << " skel_id: " << render_scene.meshes[meshIdx].skel_id; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); if (render_scene.meshes[meshIdx].material_id > -1) { pScene->mMeshes[meshIdx]->mMaterialIndex = render_scene.meshes[meshIdx].material_id; } @@ -216,14 +238,7 @@ void USDImporterImplTinyusdz::InternReadFile( normalsForMesh(render_scene, pScene, meshIdx, nameWExt); materialsForMesh(render_scene, pScene, meshIdx, nameWExt); uvsForMesh(render_scene, pScene, meshIdx, nameWExt); - pScene->mRootNode->mMeshes[meshIdx] = static_cast(meshIdx); } - nodes(render_scene, pScene, nameWExt); - materials(render_scene, pScene, nameWExt); - textures(render_scene, pScene, nameWExt); - textureImages(render_scene, pScene, nameWExt); - buffers(render_scene, pScene, nameWExt); - animations(render_scene, pScene, nameWExt); } void USDImporterImplTinyusdz::verticesForMesh( @@ -248,40 +263,13 @@ void USDImporterImplTinyusdz::facesForMesh( pScene->mMeshes[meshIdx]->mNumFaces = render_scene.meshes[meshIdx].faceVertexCounts().size(); pScene->mMeshes[meshIdx]->mFaces = new aiFace[pScene->mMeshes[meshIdx]->mNumFaces](); size_t faceVertIdxOffset = 0; -// stringstream ss; -// ss.str(""); -// ss << "facesForMesh() for model " << nameWExt << " mesh[" << meshIdx << "]; " << -// render_scene.meshes[meshIdx].faceVertexIndices().size() << " indices, " << -// render_scene.meshes[meshIdx].faceVertexCounts().size() << " counts, type: " << -// static_cast(render_scene.meshes[meshIdx].vertexArrayType) << -// ", Indexed: " << static_cast(tinyusdz::tydra::RenderMesh::VertexArrayType::Indexed) << -// ", Facevarying: " << static_cast(tinyusdz::tydra::RenderMesh::VertexArrayType::Facevarying); -// TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); - if (render_scene.meshes[meshIdx].vertexArrayType == tinyusdz::tydra::RenderMesh::VertexArrayType::Indexed) { -// ss.str(""); -// ss << "vertexArrayType: Indexed"; -// TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); - } else { -// ss.str(""); -// ss << "vertexArrayType: Facevarying"; -// TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); - } for (size_t faceIdx = 0; faceIdx < pScene->mMeshes[meshIdx]->mNumFaces; ++faceIdx) { pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices = render_scene.meshes[meshIdx].faceVertexCounts()[faceIdx]; pScene->mMeshes[meshIdx]->mFaces[faceIdx].mIndices = new unsigned int[pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices]; -// ss.str(""); -// ss << " m[" << meshIdx << "] f[" << faceIdx << "] o[" << -// faceVertIdxOffset << "] N: " << pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices << ": "; for (size_t j = 0; j < pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices; ++j) { -// ss << "i[" << j << "]: " << -// render_scene.meshes[meshIdx].faceVertexIndices()[j + faceVertIdxOffset]; -// if (j < pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices - 1) { -// ss << ", "; -// } pScene->mMeshes[meshIdx]->mFaces[faceIdx].mIndices[j] = render_scene.meshes[meshIdx].faceVertexIndices()[j + faceVertIdxOffset]; } -// TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); faceVertIdxOffset += pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices; } } @@ -291,14 +279,8 @@ void USDImporterImplTinyusdz::normalsForMesh( aiScene *pScene, size_t meshIdx, const std::string &nameWExt) { - stringstream ss; pScene->mMeshes[meshIdx]->mNormals = new aiVector3D[pScene->mMeshes[meshIdx]->mNumVertices]; const float *floatPtr = reinterpret_cast(render_scene.meshes[meshIdx].normals.get_data().data()); - ss.str(""); - ss << "normalsForMesh() for model " << nameWExt << " mesh[" << meshIdx << "]: " << - "data size: " << render_scene.meshes[meshIdx].normals.get_data().size() << - ", num verts: " << pScene->mMeshes[meshIdx]->mNumVertices; - TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); for (size_t vertIdx = 0, fpj = 0; vertIdx < pScene->mMeshes[meshIdx]->mNumVertices; ++vertIdx, fpj += 3) { pScene->mMeshes[meshIdx]->mNormals[vertIdx].x = floatPtr[fpj]; pScene->mMeshes[meshIdx]->mNormals[vertIdx].y = floatPtr[fpj + 1]; @@ -318,12 +300,7 @@ void USDImporterImplTinyusdz::uvsForMesh( aiScene *pScene, size_t meshIdx, const std::string &nameWExt) { - stringstream ss; const size_t uvSlotsCount = render_scene.meshes[meshIdx].texcoords.size(); - ss.str(""); - ss << "uvsForMesh(): uvSlotsCount for mesh[" << meshIdx << "]: " << uvSlotsCount << " w/" << - pScene->mMeshes[meshIdx]->mNumVertices << " vertices"; - TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); if (uvSlotsCount < 1) { return; } @@ -332,14 +309,7 @@ void USDImporterImplTinyusdz::uvsForMesh( for (size_t uvSlotIdx = 0; uvSlotIdx < uvSlotsCount; ++uvSlotIdx) { const auto uvsForSlot = render_scene.meshes[meshIdx].texcoords.at(uvSlotIdx); if (uvsForSlot.get_data().size() == 0) { - ss.str(""); - ss << " NOTICE: uvSlotIdx: " << uvSlotIdx << " has " << uvsForSlot.get_data().size() << " bytes"; - TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); continue; - } else { - ss.str(""); - ss << " uvSlotIdx: " << uvSlotIdx << " has " << uvsForSlot.get_data().size() << " bytes"; - TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); } const float *floatPtr = reinterpret_cast(uvsForSlot.get_data().data()); for (size_t vertIdx = 0, fpj = 0; vertIdx < pScene->mMeshes[meshIdx]->mNumVertices; ++vertIdx, fpj += 2) { @@ -349,18 +319,6 @@ void USDImporterImplTinyusdz::uvsForMesh( } } -void USDImporterImplTinyusdz::nodes( - const tinyusdz::tydra::RenderScene &render_scene, - aiScene *pScene, - const std::string &nameWExt) { - const size_t numNodes{render_scene.nodes.size()}; - (void) numNodes; // Ignore unused variable when -Werror enabled - stringstream ss; - ss.str(""); - ss << "nodes(): model" << nameWExt << ", numNodes: " << numNodes; - TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); -} - static aiColor3D *ownedColorPtrFor(const std::array &color) { aiColor3D *colorPtr = new aiColor3D(); colorPtr->r = color[0]; @@ -491,7 +449,9 @@ void USDImporterImplTinyusdz::materials( if (material.surfaceShader.ior.is_texture()) { ss << " material[" << pScene->mNumMaterials << "]: ior tex id " << material.surfaceShader.ior.textureId << "\n"; } - TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + if (!ss.str().empty()) { + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } pScene->mMaterials[pScene->mNumMaterials] = mat; ++pScene->mNumMaterials; @@ -626,16 +586,152 @@ void USDImporterImplTinyusdz::buffers( } } -void USDImporterImplTinyusdz::animations( +void USDImporterImplTinyusdz::setupNodes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + std::map &meshNodes, + const std::string &nameWExt) { + stringstream ss; + + pScene->mRootNode = nodes(render_scene, meshNodes, nameWExt); + pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; + pScene->mRootNode->mMeshes = new unsigned int[pScene->mRootNode->mNumMeshes]; + ss.str(""); + ss << "setupNodes(): pScene->mNumMeshes: " << pScene->mNumMeshes; + if (pScene->mRootNode != nullptr) { + ss << ", mRootNode->mNumMeshes: " << pScene->mRootNode->mNumMeshes; + } + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + + for (size_t meshIdx = 0; meshIdx < pScene->mNumMeshes; meshIdx++) { + pScene->mRootNode->mMeshes[meshIdx] = meshIdx; + } + +} + +aiNode *USDImporterImplTinyusdz::nodes( + const tinyusdz::tydra::RenderScene &render_scene, + std::map &meshNodes, + const std::string &nameWExt) { + const size_t numNodes{render_scene.nodes.size()}; + (void) numNodes; // Ignore unused variable when -Werror enabled + stringstream ss; + ss.str(""); + ss << "nodes(): model" << nameWExt << ", numNodes: " << numNodes; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + return nodesRecursive(nullptr, render_scene.nodes[0], meshNodes); +} + +using Assimp::tinyusdzNodeTypeFor; +using Assimp::tinyUsdzMat4ToAiMat4; +using tinyusdz::tydra::NodeType; +aiNode *USDImporterImplTinyusdz::nodesRecursive( + aiNode *pNodeParent, + const tinyusdz::tydra::Node &node, + std::map &meshNodes) { + stringstream ss; + aiNode *cNode = new aiNode(); + cNode->mParent = pNodeParent; + cNode->mName.Set(node.prim_name); + cNode->mTransformation = tinyUsdzMat4ToAiMat4(node.local_matrix.m); + ss.str(""); + ss << "nodesRecursive(): node " << cNode->mName.C_Str() << + " type: |" << tinyusdzNodeTypeFor(node.nodeType) << + "|, disp " << node.display_name << ", abs " << node.abs_path; + if (cNode->mParent != nullptr) { + ss << " (parent " << cNode->mParent->mName.C_Str() << ")"; + } + ss << " has " << node.children.size() << " children"; + if (node.id > -1) { + ss << "\n node mesh id: " << node.id << " (node type: " << tinyusdzNodeTypeFor(node.nodeType) << ")"; + meshNodes[node.id] = node; + } + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + if (!node.children.empty()) { + cNode->mNumChildren = node.children.size(); + cNode->mChildren = new aiNode *[cNode->mNumChildren]; + } + + size_t i{0}; + for (const auto &childNode: node.children) { + cNode->mChildren[i] = nodesRecursive(cNode, childNode, meshNodes); + ++i; + } + return cNode; +} + +void USDImporterImplTinyusdz::sanityCheckNodesRecursive( + aiNode *cNode) { + stringstream ss; + ss.str(""); + ss << "sanityCheckNodesRecursive(): node " << cNode->mName.C_Str(); + if (cNode->mParent != nullptr) { + ss << " (parent " << cNode->mParent->mName.C_Str() << ")"; + } + ss << " has " << cNode->mNumChildren << " children"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + for (size_t i = 0; i < cNode->mNumChildren; ++i) { + sanityCheckNodesRecursive(cNode->mChildren[i]); + } +} + +void USDImporterImplTinyusdz::setupBlendShapes( const tinyusdz::tydra::RenderScene &render_scene, aiScene *pScene, const std::string &nameWExt) { - const size_t numAnimations{render_scene.animations.size()}; - (void) numAnimations; // Ignore unused variable when -Werror enabled stringstream ss; ss.str(""); - ss << "animations(): model" << nameWExt << ", numAnimations: " << numAnimations; + ss << "setupBlendShapes(): iterating over " << pScene->mNumMeshes << " meshes for model" << nameWExt; TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + for (size_t meshIdx = 0; meshIdx < pScene->mNumMeshes; meshIdx++) { + blendShapesForMesh(render_scene, pScene, meshIdx, nameWExt); + } +} + +void USDImporterImplTinyusdz::blendShapesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + stringstream ss; + const size_t numBlendShapeTargets{render_scene.meshes[meshIdx].targets.size()}; + (void) numBlendShapeTargets; // Ignore unused variable when -Werror enabled + ss.str(""); + ss << " blendShapesForMesh(): mesh[" << meshIdx << "], numBlendShapeTargets: " << numBlendShapeTargets; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + if (numBlendShapeTargets > 0) { + pScene->mMeshes[meshIdx]->mNumAnimMeshes = numBlendShapeTargets; + pScene->mMeshes[meshIdx]->mAnimMeshes = new aiAnimMesh *[pScene->mMeshes[meshIdx]->mNumAnimMeshes]; + } + auto mapIter = render_scene.meshes[meshIdx].targets.begin(); + size_t animMeshIdx{0}; + for (; mapIter != render_scene.meshes[meshIdx].targets.end(); ++mapIter) { + const std::string name{mapIter->first}; + const tinyusdz::tydra::ShapeTarget shapeTarget{mapIter->second}; + pScene->mMeshes[meshIdx]->mAnimMeshes[animMeshIdx] = aiCreateAnimMesh(pScene->mMeshes[meshIdx]); + ss.str(""); + ss << " mAnimMeshes[" << animMeshIdx << "]: mNumVertices: " << pScene->mMeshes[meshIdx]->mAnimMeshes[animMeshIdx]->mNumVertices << + ", target: " << shapeTarget.pointIndices.size() << " pointIndices, " << shapeTarget.pointOffsets.size() << + " pointOffsets, " << shapeTarget.normalOffsets.size() << " normalOffsets"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + for (size_t iVert = 0; iVert < shapeTarget.pointOffsets.size(); ++iVert) { + pScene->mMeshes[meshIdx]->mAnimMeshes[animMeshIdx]->mVertices[shapeTarget.pointIndices[iVert]] += + tinyUsdzScaleOrPosToAssimp(shapeTarget.pointOffsets[iVert]); + } + for (size_t iVert = 0; iVert < shapeTarget.normalOffsets.size(); ++iVert) { + pScene->mMeshes[meshIdx]->mAnimMeshes[animMeshIdx]->mNormals[shapeTarget.pointIndices[iVert]] += + tinyUsdzScaleOrPosToAssimp(shapeTarget.normalOffsets[iVert]); + } + ss.str(""); + ss << " target[" << animMeshIdx << "]: name: " << name << ", prim_name: " << + shapeTarget.prim_name << ", abs_path: " << shapeTarget.abs_path << + ", display_name: " << shapeTarget.display_name << ", " << shapeTarget.pointIndices.size() << + " pointIndices, " << shapeTarget.pointOffsets.size() << " pointOffsets, " << + shapeTarget.normalOffsets.size() << " normalOffsets, " << shapeTarget.inbetweens.size() << + " inbetweens"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + ++animMeshIdx; + } } } // namespace Assimp diff --git a/code/AssetLib/USD/USDLoaderImplTinyusdz.h b/code/AssetLib/USD/USDLoaderImplTinyusdz.h index c31303733..637c58686 100644 --- a/code/AssetLib/USD/USDLoaderImplTinyusdz.h +++ b/code/AssetLib/USD/USDLoaderImplTinyusdz.h @@ -46,6 +46,7 @@ Copyright (c) 2006-2024, assimp team #define AI_USDLOADER_IMPL_TINYUSDZ_H_INCLUDED #include +#include #include #include #include @@ -63,6 +64,11 @@ public: aiScene *pScene, IOSystem *pIOHandler); + void meshes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + void verticesForMesh( const tinyusdz::tydra::RenderScene &render_scene, aiScene *pScene, @@ -93,11 +99,6 @@ public: size_t meshIdx, const std::string &nameWExt); - void nodes( - const tinyusdz::tydra::RenderScene &render_scene, - aiScene *pScene, - const std::string &nameWExt); - void materials( const tinyusdz::tydra::RenderScene &render_scene, aiScene *pScene, @@ -118,9 +119,35 @@ public: aiScene *pScene, const std::string &nameWExt); - void animations( + void setupNodes( const tinyusdz::tydra::RenderScene &render_scene, aiScene *pScene, + std::map &meshNodes, + const std::string &nameWExt + ); + + aiNode *nodes( + const tinyusdz::tydra::RenderScene &render_scene, + std::map &meshNodes, + const std::string &nameWExt); + + aiNode *nodesRecursive( + aiNode *pNodeParent, + const tinyusdz::tydra::Node &node, + std::map &meshNodes); + + void sanityCheckNodesRecursive( + aiNode *pNode); + + void setupBlendShapes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void blendShapesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, const std::string &nameWExt); }; } // namespace Assimp diff --git a/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp b/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp new file mode 100644 index 000000000..2f10db32e --- /dev/null +++ b/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp @@ -0,0 +1,110 @@ +#ifndef ASSIMP_BUILD_NO_USD_IMPORTER +#include "USDLoaderImplTinyusdzHelper.h" + +#include "../../../contrib/tinyusdz/assimp_tinyusdz_logging.inc" + +namespace { +//const char *const TAG = "tinyusdz helper"; +} + +using ChannelType = tinyusdz::tydra::AnimationChannel::ChannelType; +std::string Assimp::tinyusdzAnimChannelTypeFor(ChannelType animChannel) { + switch (animChannel) { + case ChannelType::Transform: { + return "Transform"; + } + case ChannelType::Translation: { + return "Translation"; + } + case ChannelType::Rotation: { + return "Rotation"; + } + case ChannelType::Scale: { + return "Scale"; + } + case ChannelType::Weight: { + return "Weight"; + } + default: + return "Invalid"; + } +} + +using tinyusdz::tydra::NodeType; +std::string Assimp::tinyusdzNodeTypeFor(NodeType type) { + switch (type) { + case NodeType::Xform: { + return "Xform"; + } + case NodeType::Mesh: { + return "Mesh"; + } + case NodeType::Camera: { + return "Camera"; + } + case NodeType::Skeleton: { + return "Skeleton"; + } + case NodeType::PointLight: { + return "PointLight"; + } + case NodeType::DirectionalLight: { + return "DirectionalLight"; + } + case NodeType::EnvmapLight: { + return "EnvmapLight"; + } + default: + return "Invalid"; + } +} + +aiMatrix4x4 Assimp::tinyUsdzMat4ToAiMat4(const double matIn[4][4]) { + aiMatrix4x4 matOut; + matOut.a1 = matIn[0][0]; + matOut.a2 = matIn[0][1]; + matOut.a3 = matIn[0][2]; + matOut.a4 = matIn[0][3]; + matOut.b1 = matIn[1][0]; + matOut.b2 = matIn[1][1]; + matOut.b3 = matIn[1][2]; + matOut.b4 = matIn[1][3]; + matOut.c1 = matIn[2][0]; + matOut.c2 = matIn[2][1]; + matOut.c3 = matIn[2][2]; + matOut.c4 = matIn[2][3]; + matOut.d1 = matIn[3][0]; + matOut.d2 = matIn[3][1]; + matOut.d3 = matIn[3][2]; + matOut.d4 = matIn[3][3]; +// matOut.a1 = matIn[0][0]; +// matOut.a2 = matIn[1][0]; +// matOut.a3 = matIn[2][0]; +// matOut.a4 = matIn[3][0]; +// matOut.b1 = matIn[0][1]; +// matOut.b2 = matIn[1][1]; +// matOut.b3 = matIn[2][1]; +// matOut.b4 = matIn[3][1]; +// matOut.c1 = matIn[0][2]; +// matOut.c2 = matIn[1][2]; +// matOut.c3 = matIn[2][2]; +// matOut.c4 = matIn[3][2]; +// matOut.d1 = matIn[0][3]; +// matOut.d2 = matIn[1][3]; +// matOut.d3 = matIn[2][3]; +// matOut.d4 = matIn[3][3]; + return matOut; +} + +aiVector3D Assimp::tinyUsdzScaleOrPosToAssimp(const std::array &scaleOrPosIn) { + return aiVector3D(scaleOrPosIn[0], scaleOrPosIn[1], scaleOrPosIn[2]); +} + +aiQuaternion Assimp::tinyUsdzQuatToAiQuat(const std::array &quatIn) { + // tinyusdz "quat" is x,y,z,w + // aiQuaternion is w,x,y,z + return aiQuaternion( + quatIn[3], quatIn[0], quatIn[1], quatIn[2]); +} + +#endif // !! ASSIMP_BUILD_NO_USD_IMPORTER diff --git a/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h b/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h new file mode 100644 index 000000000..ee6d96a06 --- /dev/null +++ b/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h @@ -0,0 +1,29 @@ +#pragma once +#ifndef AI_USDLOADER_IMPL_TINYUSDZ_HELPER_H_INCLUDED +#define AI_USDLOADER_IMPL_TINYUSDZ_HELPER_H_INCLUDED + +#include +#include +#include +#include "tinyusdz.hh" +#include "tydra/render-data.hh" + +namespace Assimp { + +std::string tinyusdzAnimChannelTypeFor( + tinyusdz::tydra::AnimationChannel::ChannelType animChannel); +std::string tinyusdzNodeTypeFor(tinyusdz::tydra::NodeType type); +aiMatrix4x4 tinyUsdzMat4ToAiMat4(const double matIn[4][4]); + +aiVector3D tinyUsdzScaleOrPosToAssimp(const std::array &scaleOrPosIn); + +/** + * Convert quaternion from tinyusdz "quat" to assimp "aiQuaternion" type + * + * @param quatIn tinyusdz float[4] in x,y,z,w order + * @return assimp aiQuaternion converted from input + */ +aiQuaternion tinyUsdzQuatToAiQuat(const std::array &quatIn); + +} // namespace Assimp +#endif // AI_USDLOADER_IMPL_TINYUSDZ_HELPER_H_INCLUDED diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 5b59d6284..d0b3094c6 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -817,6 +817,8 @@ ADD_ASSIMP_IMPORTER( USD AssetLib/USD/USDLoader.h AssetLib/USD/USDLoaderImplTinyusdz.cpp AssetLib/USD/USDLoaderImplTinyusdz.h + AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp + AssetLib/USD/USDLoaderImplTinyusdzHelper.h AssetLib/USD/USDLoaderUtil.cpp AssetLib/USD/USDLoaderUtil.h ) diff --git a/code/Common/BaseImporter.cpp b/code/Common/BaseImporter.cpp index 3a4c7c329..5c70cc27e 100644 --- a/code/Common/BaseImporter.cpp +++ b/code/Common/BaseImporter.cpp @@ -250,9 +250,10 @@ void BaseImporter::GetExtensionList(std::set &extensions) { /*static*/ bool BaseImporter::SimpleExtensionCheck(const std::string &pFile, const char *ext0, const char *ext1, - const char *ext2) { + const char *ext2, + const char *ext3) { std::set extensions; - for (const char* ext : {ext0, ext1, ext2}) { + for (const char* ext : {ext0, ext1, ext2, ext3}) { if (ext == nullptr) continue; extensions.emplace(ext); } diff --git a/contrib/tinyusdz/tinyusdz_repo/src/pprinter.cc b/contrib/tinyusdz/tinyusdz_repo/src/pprinter.cc index 47d4ae379..2d715e80d 100644 --- a/contrib/tinyusdz/tinyusdz_repo/src/pprinter.cc +++ b/contrib/tinyusdz/tinyusdz_repo/src/pprinter.cc @@ -2834,7 +2834,7 @@ std::string to_string(const GeomMesh &mesh, const uint32_t indent, if (mesh.skeleton) { ss << print_relationship(mesh.skeleton.value(), mesh.skeleton.value().get_listedit_qual(), - /* custom */ false, "skel:skeketon", indent + 1); + /* custom */ false, "skel:skeleton", indent + 1); } ss << print_typed_attr(mesh.blendShapes, "skel:blendShapes", indent + 1); diff --git a/contrib/tinyusdz/tinyusdz_repo/src/prim-types.hh b/contrib/tinyusdz/tinyusdz_repo/src/prim-types.hh index 3fc3bd139..59fc8f1df 100644 --- a/contrib/tinyusdz/tinyusdz_repo/src/prim-types.hh +++ b/contrib/tinyusdz/tinyusdz_repo/src/prim-types.hh @@ -1481,6 +1481,8 @@ class TypedAttribute { return (*this); } + // TODO: Move constructor, Move assignment + void set_value(const T &v) { _attrib = v; _value_empty = false; } const nonstd::optional get_value() const { diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/obj-export.cc b/contrib/tinyusdz/tinyusdz_repo/src/tydra/obj-export.cc index a71526231..2076c9147 100644 --- a/contrib/tinyusdz/tinyusdz_repo/src/tydra/obj-export.cc +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/obj-export.cc @@ -209,8 +209,9 @@ bool export_to_obj(const RenderScene &scene, const int mesh_id, uint32_t mat_id = uint32_t(std::get<0>(group.second)); ss << "newmtl " << scene.materials[mat_id].name << "\n"; - // Diffuse only - // TODO: Emit more PBR material + // Original MTL spec: https://paulbourke.net/dataformats/mtl/ + // Emit PBR material: https://github.com/tinyobjloader/tinyobjloader/blob/release/pbr-mtl.md + if (scene.materials[mat_id].surfaceShader.diffuseColor.is_texture()) { int32_t texId = scene.materials[mat_id].surfaceShader.diffuseColor.textureId; if ((texId < 0) || (texId >= int(scene.textures.size()))) { @@ -232,6 +233,226 @@ bool export_to_obj(const RenderScene &scene, const int mesh_id, ss << "Kd " << col[0] << " " << col[1] << " " << col[2] << "\n"; } + if (scene.materials[mat_id].surfaceShader.useSpecularWorkFlow) { + if (scene.materials[mat_id].surfaceShader.specularColor.is_texture()) { + int32_t texId = scene.materials[mat_id].surfaceShader.specularColor.textureId; + if ((texId < 0) || (texId >= int(scene.textures.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid texture id {}. scene.textures.size = {}", texId, scene.textures.size())); + } + + int64_t imageId = scene.textures[size_t(texId)].texture_image_id; + if ((imageId < 0) || (imageId >= int64_t(scene.images.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid image id {}. scene.images.size = {}", imageId, scene.images.size())); + } + + std::string texname = scene.images[size_t(imageId)].asset_identifier; + if (texname.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Filename for image id {} is empty.", imageId)); + } + ss << "map_Ks " << texname << "\n"; + } else { + const auto col = scene.materials[mat_id].surfaceShader.specularColor.value; + ss << "Ks " << col[0] << " " << col[1] << " " << col[2] << "\n"; + } + } else { + + if (scene.materials[mat_id].surfaceShader.metallic.is_texture()) { + int32_t texId = scene.materials[mat_id].surfaceShader.metallic.textureId; + if ((texId < 0) || (texId >= int(scene.textures.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid texture id {}. scene.textures.size = {}", texId, scene.textures.size())); + } + + int64_t imageId = scene.textures[size_t(texId)].texture_image_id; + if ((imageId < 0) || (imageId >= int64_t(scene.images.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid image id {}. scene.images.size = {}", imageId, scene.images.size())); + } + + std::string texname = scene.images[size_t(imageId)].asset_identifier; + if (texname.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Filename for image id {} is empty.", imageId)); + } + ss << "map_Pm " << texname << "\n"; + } else { + const auto f = scene.materials[mat_id].surfaceShader.metallic.value; + ss << "Pm " << f << "\n"; + } + } + + if (scene.materials[mat_id].surfaceShader.roughness.is_texture()) { + int32_t texId = scene.materials[mat_id].surfaceShader.roughness.textureId; + if ((texId < 0) || (texId >= int(scene.textures.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid texture id {}. scene.textures.size = {}", texId, scene.textures.size())); + } + + int64_t imageId = scene.textures[size_t(texId)].texture_image_id; + if ((imageId < 0) || (imageId >= int64_t(scene.images.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid image id {}. scene.images.size = {}", imageId, scene.images.size())); + } + + std::string texname = scene.images[size_t(imageId)].asset_identifier; + if (texname.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Filename for image id {} is empty.", imageId)); + } + ss << "map_Pr " << texname << "\n"; + } else { + const auto f = scene.materials[mat_id].surfaceShader.roughness.value; + ss << "Pr " << f << "\n"; + } + + if (scene.materials[mat_id].surfaceShader.emissiveColor.is_texture()) { + int32_t texId = scene.materials[mat_id].surfaceShader.emissiveColor.textureId; + if ((texId < 0) || (texId >= int(scene.textures.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid texture id {}. scene.textures.size = {}", texId, scene.textures.size())); + } + + int64_t imageId = scene.textures[size_t(texId)].texture_image_id; + if ((imageId < 0) || (imageId >= int64_t(scene.images.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid image id {}. scene.images.size = {}", imageId, scene.images.size())); + } + + std::string texname = scene.images[size_t(imageId)].asset_identifier; + if (texname.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Filename for image id {} is empty.", imageId)); + } + ss << "map_Ke " << texname << "\n"; + } else { + const auto col = scene.materials[mat_id].surfaceShader.emissiveColor.value; + ss << "Ke " << col[0] << " " << col[1] << " " << col[2] << "\n"; + } + + + if (scene.materials[mat_id].surfaceShader.opacity.is_texture()) { + int32_t texId = scene.materials[mat_id].surfaceShader.opacity.textureId; + if ((texId < 0) || (texId >= int(scene.textures.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid texture id {}. scene.textures.size = {}", texId, scene.textures.size())); + } + + int64_t imageId = scene.textures[size_t(texId)].texture_image_id; + if ((imageId < 0) || (imageId >= int64_t(scene.images.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid image id {}. scene.images.size = {}", imageId, scene.images.size())); + } + + std::string texname = scene.images[size_t(imageId)].asset_identifier; + if (texname.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Filename for image id {} is empty.", imageId)); + } + ss << "map_d " << texname << "\n"; + } else { + const auto f = scene.materials[mat_id].surfaceShader.opacity.value; + ss << "d " << f << "\n"; + } + + // emit as cleacoat thickness + if (scene.materials[mat_id].surfaceShader.clearcoat.is_texture()) { + int32_t texId = scene.materials[mat_id].surfaceShader.clearcoat.textureId; + if ((texId < 0) || (texId >= int(scene.textures.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid texture id {}. scene.textures.size = {}", texId, scene.textures.size())); + } + + int64_t imageId = scene.textures[size_t(texId)].texture_image_id; + if ((imageId < 0) || (imageId >= int64_t(scene.images.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid image id {}. scene.images.size = {}", imageId, scene.images.size())); + } + + std::string texname = scene.images[size_t(imageId)].asset_identifier; + if (texname.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Filename for image id {} is empty.", imageId)); + } + ss << "map_Pc " << texname << "\n"; + } else { + const auto f = scene.materials[mat_id].surfaceShader.clearcoat.value; + ss << "Pc " << f << "\n"; + } + + if (scene.materials[mat_id].surfaceShader.clearcoatRoughness.is_texture()) { + int32_t texId = scene.materials[mat_id].surfaceShader.clearcoatRoughness.textureId; + if ((texId < 0) || (texId >= int(scene.textures.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid texture id {}. scene.textures.size = {}", texId, scene.textures.size())); + } + + int64_t imageId = scene.textures[size_t(texId)].texture_image_id; + if ((imageId < 0) || (imageId >= int64_t(scene.images.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid image id {}. scene.images.size = {}", imageId, scene.images.size())); + } + + std::string texname = scene.images[size_t(imageId)].asset_identifier; + if (texname.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Filename for image id {} is empty.", imageId)); + } + ss << "map_Pcr " << texname << "\n"; + } else { + const auto f = scene.materials[mat_id].surfaceShader.clearcoatRoughness.value; + ss << "Pcr " << f << "\n"; + } + + if (scene.materials[mat_id].surfaceShader.ior.is_texture()) { + int32_t texId = scene.materials[mat_id].surfaceShader.ior.textureId; + if ((texId < 0) || (texId >= int(scene.textures.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid texture id {}. scene.textures.size = {}", texId, scene.textures.size())); + } + + int64_t imageId = scene.textures[size_t(texId)].texture_image_id; + if ((imageId < 0) || (imageId >= int64_t(scene.images.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid image id {}. scene.images.size = {}", imageId, scene.images.size())); + } + + std::string texname = scene.images[size_t(imageId)].asset_identifier; + if (texname.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Filename for image id {} is empty.", imageId)); + } + // map_Ni is not in original mtl definition + ss << "map_Ni " << texname << "\n"; + } else { + const auto f = scene.materials[mat_id].surfaceShader.clearcoatRoughness.value; + ss << "Ni " << f << "\n"; + } + + if (scene.materials[mat_id].surfaceShader.occlusion.is_texture()) { + int32_t texId = scene.materials[mat_id].surfaceShader.occlusion.textureId; + if ((texId < 0) || (texId >= int(scene.textures.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid texture id {}. scene.textures.size = {}", texId, scene.textures.size())); + } + + int64_t imageId = scene.textures[size_t(texId)].texture_image_id; + if ((imageId < 0) || (imageId >= int64_t(scene.images.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid image id {}. scene.images.size = {}", imageId, scene.images.size())); + } + + std::string texname = scene.images[size_t(imageId)].asset_identifier; + if (texname.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Filename for image id {} is empty.", imageId)); + } + // Use map_ao? + ss << "map_Ka " << texname << "\n"; + } else { + const auto f = scene.materials[mat_id].surfaceShader.occlusion.value; + ss << "Ka " << f << "\n"; + } + + if (scene.materials[mat_id].surfaceShader.ior.is_texture()) { + int32_t texId = scene.materials[mat_id].surfaceShader.ior.textureId; + if ((texId < 0) || (texId >= int(scene.textures.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid texture id {}. scene.textures.size = {}", texId, scene.textures.size())); + } + + int64_t imageId = scene.textures[size_t(texId)].texture_image_id; + if ((imageId < 0) || (imageId >= int64_t(scene.images.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid image id {}. scene.images.size = {}", imageId, scene.images.size())); + } + + std::string texname = scene.images[size_t(imageId)].asset_identifier; + if (texname.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Filename for image id {} is empty.", imageId)); + } + // map_Ni is not in original mtl definition + ss << "map_Ni " << texname << "\n"; + } else { + const auto f = scene.materials[mat_id].surfaceShader.clearcoatRoughness.value; + ss << "Ni " << f << "\n"; + } + + // TODO: opacityThreshold + ss << "\n"; } ss << "# " << face_groups.size() << " materials.\n"; diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.cc b/contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.cc index a2fb129a6..979eac378 100644 --- a/contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.cc +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.cc @@ -11,6 +11,10 @@ // - [ ] linear sRGB <-> linear DisplayP3 // - [x] Compute tangentes and binormals // - [x] displayColor, displayOpacity primvar(vertex color) +// - [x] Support Skeleton +// - [ ] Support SkelAnimation +// - [x] joint animation +// - [x] blendshape animation // - [ ] Support Inbetween BlendShape // - [ ] Support material binding collection(Collection API) // - [ ] Support multiple skel animation @@ -42,8 +46,6 @@ #include "external/tiny-color-io.h" #endif -#include "../../../assimp_tinyusdz_logging.inc" - #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Weverything" @@ -716,6 +718,7 @@ static bool TryConvertFacevaryingToVertex( if (!ret) { \ return false; \ } \ + dst->name = src.name; \ dst->elementSize = 1; \ dst->format = src.format; \ dst->variability = VertexVariability::Vertex; \ @@ -735,6 +738,7 @@ static bool TryConvertFacevaryingToVertex( if (!ret) { \ return false; \ } \ + dst->name = src.name; \ dst->elementSize = 1; \ dst->format = src.format; \ dst->variability = VertexVariability::Vertex; \ @@ -754,6 +758,7 @@ static bool TryConvertFacevaryingToVertex( if (!ret) { \ return false; \ } \ + dst->name = src.name; \ dst->elementSize = 1; \ dst->format = src.format; \ dst->variability = VertexVariability::Vertex; \ @@ -2205,18 +2210,27 @@ static bool ComputeNormals(const std::vector &vertices, fmt::format("Invalid face num {} at faceVertexCounts[{}]", nv, f)); } - uint32_t vidx0 = faceVertexIndices[faceVertexIndexOffset + f + 0]; - uint32_t vidx1 = faceVertexIndices[faceVertexIndexOffset + f + 1]; - uint32_t vidx2 = faceVertexIndices[faceVertexIndexOffset + f + 2]; - - if ((vidx0 >= vertices.size()) || (vidx1 >= vertices.size()) || - (vidx2 >= vertices.size())) { - PUSH_ERROR_AND_RETURN( - fmt::format("vertexIndex exceeds vertices.size {}", vertices.size())); - } - // For quad/polygon, first three vertices are used to compute face normal // (Assume quad/polygon plane is co-planar) + uint32_t vidx0 = faceVertexIndices[faceVertexIndexOffset + 0]; + uint32_t vidx1 = faceVertexIndices[faceVertexIndexOffset + 1]; + uint32_t vidx2 = faceVertexIndices[faceVertexIndexOffset + 2]; + + if (vidx0 >= vertices.size()) { + PUSH_ERROR_AND_RETURN( + fmt::format("vertexIndex0 {} exceeds vertices.size {}", vidx0, vertices.size())); + } + + if (vidx1 >= vertices.size()) { + PUSH_ERROR_AND_RETURN( + fmt::format("vertexIndex1 {} exceeds vertices.size {}", vidx1, vertices.size())); + } + + if (vidx2 >= vertices.size()) { + PUSH_ERROR_AND_RETURN( + fmt::format("vertexIndex2 {} exceeds vertices.size {}", vidx2, vertices.size())); + } + float area{0.0f}; value::float3 Nf = GeometricNormal(vertices[vidx0], vertices[vidx1], vertices[vidx2], area); @@ -2634,6 +2648,7 @@ bool RenderSceneConverter::BuildVertexIndicesImpl(RenderMesh &mesh) { *std::max_element(out_indices.begin(), out_indices.end()) + 1; { std::vector tmp_points(numPoints); + // TODO: Use vertex_output[i].point_index? for (size_t i = 0; i < out_point_indices.size(); i++) { if (out_point_indices[i] >= mesh.points.size()) { PUSH_ERROR_AND_RETURN("Internal error. point index out-of-range."); @@ -2679,7 +2694,60 @@ bool RenderSceneConverter::BuildVertexIndicesImpl(RenderMesh &mesh) { mesh.joint_and_weights.jointWeights.swap(tmp_weights); } - // TODO: Reorder BlendShape points + if (mesh.targets.size()) { + // For BlendShape, reordering pointIndices, pointOffsets and normalOffsets is not enough. + // Some points could be duplicated, so we need to find a mapping of org pointIdx -> pointIdx list in reordered points, + // Then splat point attributes accordingly. + + // org pointIdx -> List of pointIdx in reordered points. + std::unordered_map> pointIdxRemap; + + for (size_t i = 0; i < vertex_output.size(); i++) { + pointIdxRemap[vertex_output.point_indices[i]].push_back(uint32_t(i)); + } + + for (auto &target : mesh.targets) { + + std::vector tmpPointOffsets; + std::vector tmpNormalOffsets; + std::vector tmpPointIndices; + + for (size_t i = 0; i < target.second.pointIndices.size(); i++) { + + uint32_t orgPointIdx = target.second.pointIndices[i]; + if (!pointIdxRemap.count(orgPointIdx)) { + PUSH_ERROR_AND_RETURN("Invalid pointIndices value."); + } + const std::vector &dstPointIndices = pointIdxRemap.at(orgPointIdx); + + for (size_t k = 0; k < dstPointIndices.size(); k++) { + if (target.second.pointOffsets.size()) { + if (i >= target.second.pointOffsets.size()) { + PUSH_ERROR_AND_RETURN("Invalid pointOffsets.size."); + } + tmpPointOffsets.push_back(target.second.pointOffsets[i]); + } + if (target.second.normalOffsets.size()) { + if (i >= target.second.normalOffsets.size()) { + PUSH_ERROR_AND_RETURN("Invalid normalOffsets.size."); + } + tmpNormalOffsets.push_back(target.second.normalOffsets[i]); + } + + tmpPointIndices.push_back(dstPointIndices[k]); + } + } + + target.second.pointIndices.swap(tmpPointIndices); + target.second.pointOffsets.swap(tmpPointOffsets); + target.second.normalOffsets.swap(tmpNormalOffsets); + + } + + // TODO: Inbetween BlendShapes + + } + } // Other 'facevarying' attributes are now 'vertex' variability @@ -3563,6 +3631,7 @@ bool RenderSceneConverter::ConvertMesh( dst.joint_and_weights.elementSize = int(jointIndicesElementSize); if (mesh.skeleton.has_value()) { + DCOUT("Convert Skeleton"); Path skelPath; if (mesh.skeleton.value().is_path()) { @@ -3579,7 +3648,32 @@ bool RenderSceneConverter::ConvertMesh( } if (skelPath.is_valid()) { - // TODO + SkelHierarchy skel; + nonstd::optional anim; + if (!ConvertSkeletonImpl(env, mesh, &skel, &anim)) { + return false; + } + DCOUT("Converted skeleton attached to : " << abs_path); + + auto it = std::find_if(skeletons.begin(), skeletons.end(), [&abs_path](const SkelHierarchy &sk) { + return sk.abs_path == abs_path.full_path_name(); + }); + + if (anim) { + skel.anim_id = int(animations.size()); + animations.emplace_back(anim.value()); + } + + int skel_id{0}; + if (it != skeletons.end()) { + skel_id = int(std::distance(skeletons.begin(), it)); + } else { + skel_id = int(skeletons.size()); + skeletons.emplace_back(std::move(skel)); + } + + dst.skel_id = skel_id; + } } @@ -3714,6 +3808,7 @@ bool RenderSceneConverter::ConvertMesh( dst.normals.format = VertexAttributeFormat::Vec3; dst.normals.stride = 0; dst.normals.indices.clear(); + dst.normals.name = "normals"; if (!is_single_indexable) { auto result = VertexToFaceVarying( @@ -3814,6 +3909,8 @@ bool RenderSceneConverter::ConvertMesh( } } + dst.is_single_indexable = is_single_indexable; + dst.prim_name = mesh.name; dst.abs_path = abs_path.full_path_name(); dst.display_name = mesh.metas().displayName.value_or(""); @@ -5122,181 +5219,355 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e const Path &abs_path, const SkelAnimation &skelAnim, Animation *anim_out) { - // The spec says + // The spec says // """ // An animation source is only valid if its translation, rotation, and scale components are all authored, storing arrays size to the same size as the authored joints array. // """ - - // NOTE: fortunately USD SkelAnimation uses quaternions for rotations - // anim_out->channels.rotations - - (void)anim_out; - - AnimationChannel channel_txs; channel_txs.type = AnimationChannel::ChannelType::Translation; - AnimationChannel channel_rots; channel_rots.type = AnimationChannel::ChannelType::Rotation; - AnimationChannel channel_scales; channel_scales.type = AnimationChannel::ChannelType::Scale; - - if (!skelAnim.joints.authored()) { - PUSH_ERROR_AND_RETURN(fmt::format("`joints` is not authored for SkelAnimation Prim : {}", abs_path)); - } + // + // SkelAnimation contains + // - Joint animations(translation, rotation, scale) + // - BlendShape animations(weights) std::vector joints; - if (!EvaluateTypedAttribute(env.stage, skelAnim.joints, "joints", &joints, &_err)) { - PUSH_ERROR_AND_RETURN(fmt::format("Failed to evaluate `joints` in SkelAnimation Prim : {}", abs_path)); - } - - if (!skelAnim.rotations.authored() || - !skelAnim.translations.authored() || - !skelAnim.scales.authored()) { - - PUSH_ERROR_AND_RETURN(fmt::format("`translations`, `rotations` and `scales` must be all authored for SkelAnimation Prim {}. authored flags: translations {}, rotations {}, scales {}", abs_path, skelAnim.translations.authored() ? "yes" : "no", - skelAnim.rotations.authored() ? "yes" : "no", - skelAnim.scales.authored() ? "yes" : "no")); - } - - - Animatable> translations; - if (!skelAnim.translations.get_value(&translations)) { - PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `translations` attribute of SkelAnimation. Maybe ValueBlock or connection? : {}", abs_path)); - } - - Animatable> rotations; - if (!skelAnim.rotations.get_value(&rotations)) { - PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `rotations` attribute of SkelAnimation. Maybe ValueBlock or connection? : {}", abs_path)); - } - - Animatable> scales; - if (!skelAnim.scales.get_value(&scales)) { - PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `scales` attribute of SkelAnimation. Maybe ValueBlock or connection? : {}", abs_path)); - } - - bool is_translations_timesamples = false; - bool is_rotations_timesamples = false; - bool is_scales_timesamples = false; - - if (translations.is_timesamples()) { - const TypedTimeSamples> &ts_txs = translations.get_timesamples(); - - if (ts_txs.get_samples().empty()) { - PUSH_ERROR_AND_RETURN(fmt::format("`translations` timeSamples in SkelAnimation is empty : {}", abs_path)); + if (skelAnim.joints.authored()) { + if (!EvaluateTypedAttribute(env.stage, skelAnim.joints, "joints", &joints, &_err)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to evaluate `joints` in SkelAnimation Prim : {}", abs_path)); } - for (const auto &sample : ts_txs.get_samples()) { - AnimationSample> dst; - if (!sample.blocked) { - // length check - if (sample.value.size() != joints.size()) { - PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} translations.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path)); + if (!skelAnim.rotations.authored() || + !skelAnim.translations.authored() || + !skelAnim.scales.authored()) { + + PUSH_ERROR_AND_RETURN(fmt::format("`translations`, `rotations` and `scales` must be all authored for SkelAnimation Prim {}. authored flags: translations {}, rotations {}, scales {}", abs_path, skelAnim.translations.authored() ? "yes" : "no", + skelAnim.rotations.authored() ? "yes" : "no", + skelAnim.scales.authored() ? "yes" : "no")); + } + } + + // TODO: inbetweens BlendShape + std::vector blendShapes; + if (skelAnim.blendShapes.authored()) { + if (!EvaluateTypedAttribute(env.stage, skelAnim.blendShapes, "blendShapes", &blendShapes, &_err)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to evaluate `blendShapes` in SkelAnimation Prim : {}", abs_path)); + } + + if (!skelAnim.blendShapeWeights.authored()) { + PUSH_ERROR_AND_RETURN(fmt::format("`blendShapeWeights` must be authored for SkelAnimation Prim {}", abs_path)); + } + + } + + + // + // Reorder values[channels][timeCode][jointId] into values[jointId][channels][timeCode] + // + + std::map> channelMap; + + // Joint animations + if (joints.size()) { + StringAndIdMap jointIdMap; + + for (const auto &joint : joints) { + uint64_t id = jointIdMap.size(); + jointIdMap.add(joint.str(), id); + } + + Animatable> translations; + if (!skelAnim.translations.get_value(&translations)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `translations` attribute of SkelAnimation. Maybe ValueBlock or connection? : {}", abs_path)); + } + + Animatable> rotations; + if (!skelAnim.rotations.get_value(&rotations)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `rotations` attribute of SkelAnimation. Maybe ValueBlock or connection? : {}", abs_path)); + } + + Animatable> scales; + if (!skelAnim.scales.get_value(&scales)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `scales` attribute of SkelAnimation. Maybe ValueBlock or connection? : {}", abs_path)); + } + + // + // NOTE: When both timeSamples and default value are authored, timeSamples wins. + // + bool is_translations_timesamples = false; + bool is_rotations_timesamples = false; + bool is_scales_timesamples = false; + + if (translations.is_timesamples()) { + DCOUT("Convert ttranslations"); + const TypedTimeSamples> &ts_txs = translations.get_timesamples(); + + if (ts_txs.get_samples().empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("`translations` timeSamples in SkelAnimation is empty : {}", abs_path)); + } + + for (const auto &sample : ts_txs.get_samples()) { + if (!sample.blocked) { + // length check + if (sample.value.size() != joints.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} translations.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path)); + } + + for (size_t j = 0; j < sample.value.size(); j++) { + AnimationSample s; + s.t = float(sample.t); + s.value = sample.value[j]; + + std::string jointName = jointIdMap.at(j); + auto &it = channelMap[jointName][AnimationChannel::ChannelType::Translation]; + if (it.translations.samples.empty()) { + it.type = AnimationChannel::ChannelType::Translation; + } + it.translations.samples.push_back(s); + } + + } + } + is_translations_timesamples = true; + } + + if (rotations.is_timesamples()) { + const TypedTimeSamples> &ts_rots = rotations.get_timesamples(); + DCOUT("Convert rotations"); + for (const auto &sample : ts_rots.get_samples()) { + if (!sample.blocked) { + if (sample.value.size() != joints.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} rotations.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path)); + } + for (size_t j = 0; j < sample.value.size(); j++) { + AnimationSample s; + s.t = float(sample.t); + s.value[0] = sample.value[j][0]; + s.value[1] = sample.value[j][1]; + s.value[2] = sample.value[j][2]; + s.value[3] = sample.value[j][3]; + + std::string jointName = jointIdMap.at(j); + auto &it = channelMap[jointName][AnimationChannel::ChannelType::Rotation]; + if (it.rotations.samples.empty()) { + it.type = AnimationChannel::ChannelType::Rotation; + } + it.rotations.samples.push_back(s); + } + + } + } + is_rotations_timesamples = true; + } + + if (scales.is_timesamples()) { + const TypedTimeSamples> &ts_scales = scales.get_timesamples(); + DCOUT("Convert scales"); + for (const auto &sample : ts_scales.get_samples()) { + if (!sample.blocked) { + if (sample.value.size() != joints.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} scales.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path)); + } + + for (size_t j = 0; j < sample.value.size(); j++) { + AnimationSample s; + s.t = float(sample.t); + s.value[0] = value::half_to_float(sample.value[j][0]); + s.value[1] = value::half_to_float(sample.value[j][1]); + s.value[2] = value::half_to_float(sample.value[j][2]); + + std::string jointName = jointIdMap.at(j); + auto &it = channelMap[jointName][AnimationChannel::ChannelType::Scale]; + if (it.scales.samples.empty()) { + it.type = AnimationChannel::ChannelType::Scale; + } + it.scales.samples.push_back(s); + } + + } + } + is_scales_timesamples = true; + } + + // value at 'default' time. + std::vector translation; + std::vector rotation; + std::vector scale; + + // Get value and also do length check for scalar(non timeSampled) animation value. + if (translations.is_scalar()) { + DCOUT("translation is not timeSampled"); + if (!translations.get_scalar(&translation)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `translations` attribute in SkelAnimation: {}", abs_path)); + } + if (translation.size() != joints.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. translations.default.size {} must be equal to joints.size {} : {}", translation.size(), joints.size(), abs_path)); + } + is_translations_timesamples = false; + } + + if (rotations.is_scalar()) { + DCOUT("rot is not timeSampled"); + std::vector _rotation; + if (!rotations.get_scalar(&_rotation)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `rotations` attribute in SkelAnimation: {}", abs_path)); + } + if (_rotation.size() != joints.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. rotations.default.size {} must be equal to joints.size {} : {}", _rotation.size(), joints.size(), abs_path)); + } + std::transform(_rotation.begin(), _rotation.end(), std::back_inserter(rotation), [](const value::quatf &v) { + value::float4 ret; + // pxrUSD's TfQuat also uses xyzw memory order. + ret[0] = v[0]; + ret[1] = v[1]; + ret[2] = v[2]; + ret[3] = v[3]; + return ret; + }); + is_rotations_timesamples = false; + } + + if (scales.is_scalar()) { + DCOUT("scale is not timeSampled"); + std::vector _scale; + if (!scales.get_scalar(&_scale)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `scales` attribute in SkelAnimation: {}", abs_path)); + } + if (_scale.size() != joints.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. scale.default.size {} must be equal to joints.size {} : {}", _scale.size(), joints.size(), abs_path)); + } + // half -> float + std::transform(_scale.begin(), _scale.end(), std::back_inserter(scale), [](const value::half3 &v) { + value::float3 ret; + ret[0] = value::half_to_float(v[0]); + ret[1] = value::half_to_float(v[1]); + ret[2] = value::half_to_float(v[2]); + return ret; + }); + is_scales_timesamples = false; + } + + if (!is_translations_timesamples) { + DCOUT("Reorder translation samples"); + // Create a channel value with single-entry + // Use USD TimeCode::Default for static sample. + for (const auto &joint : joints) { + channelMap[joint.str()][AnimationChannel::ChannelType::Translation].type = AnimationChannel::ChannelType::Translation; + + AnimationSample s; + s.t = std::numeric_limits::quiet_NaN(); + uint64_t joint_id = jointIdMap.at(joint.str()); + s.value = translation[joint_id]; + channelMap[joint.str()][AnimationChannel::ChannelType::Translation].translations.samples.clear(); + channelMap[joint.str()][AnimationChannel::ChannelType::Translation].translations.samples.push_back(s); + } + } + + if (!is_rotations_timesamples) { + DCOUT("Reorder rotation samples"); + for (const auto &joint : joints) { + channelMap[joint.str()][AnimationChannel::ChannelType::Rotation].type = AnimationChannel::ChannelType::Rotation; + + AnimationSample s; + s.t = std::numeric_limits::quiet_NaN(); + uint64_t joint_id = jointIdMap.at(joint.str()); + DCOUT("rot joint_id " << joint_id); + s.value = rotation[joint_id]; + channelMap[joint.str()][AnimationChannel::ChannelType::Rotation].rotations.samples.clear(); + channelMap[joint.str()][AnimationChannel::ChannelType::Rotation].rotations.samples.push_back(s); + } + } + + if (!is_scales_timesamples) { + DCOUT("Reorder scale samples"); + for (const auto &joint : joints) { + channelMap[joint.str()][AnimationChannel::ChannelType::Scale].type = AnimationChannel::ChannelType::Scale; + + AnimationSample s; + s.t = std::numeric_limits::quiet_NaN(); + uint64_t joint_id = jointIdMap.at(joint.str()); + s.value = scale[joint_id]; + channelMap[joint.str()][AnimationChannel::ChannelType::Scale].scales.samples.clear(); + channelMap[joint.str()][AnimationChannel::ChannelType::Scale].scales.samples.push_back(s); + } + } + } + + // BlendShape animations + if (blendShapes.size()) { + + std::map>> weightsMap; + + // Blender 4.1 may export empty bendShapeWeights. We'll accept it. + // + // float[] blendShapeWeights + if (skelAnim.blendShapeWeights.is_value_empty()) { + for (const auto &bs : blendShapes) { + AnimationSample s; + s.t = std::numeric_limits::quiet_NaN(); + s.value = 1.0f; + weightsMap[bs.str()].push_back(s); + } + } else { + + Animatable> weights; + if (!skelAnim.blendShapeWeights.get_value(&weights)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `blendShapeWeights` attribute of SkelAnimation. Maybe ValueBlock or connection? : {}", abs_path)); + } + + if (weights.is_timesamples()) { + + const TypedTimeSamples> &ts_weights = weights.get_timesamples(); + DCOUT("Convert timeSampledd weights"); + for (const auto &sample : ts_weights.get_samples()) { + if (!sample.blocked) { + if (sample.value.size() != blendShapes.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} blendShapeWeights.size {} must be equal to blendShapes.size {} : {}", sample.t, sample.value.size(), blendShapes.size(), abs_path)); + } + + for (size_t j = 0; j < sample.value.size(); j++) { + AnimationSample s; + s.t = float(sample.t); + s.value = sample.value[j]; + + const std::string &targetName = blendShapes[j].str(); + weightsMap[targetName].push_back(s); + } + + } } - dst.t = float(sample.t); - dst.value = sample.value; - channel_txs.translations.samples.push_back(dst); + } else if (weights.is_scalar()) { + std::vector ws; + if (!weights.get_scalar(&ws)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to get default value of `blendShapeWeights` attribute of SkelAnimation is invalid : {}", abs_path)); + } + + if (ws.size() != blendShapes.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("blendShapeWeights.size {} must be equal to blendShapes.size {} : {}", ws.size(), blendShapes.size(), abs_path)); + } + + for (size_t i = 0; i < blendShapes.size(); i++) { + AnimationSample s; + s.t = std::numeric_limits::quiet_NaN(); + s.value = ws[i]; + weightsMap[blendShapes[i].str()].push_back(s); + } + + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Internal error. `blendShapeWeights` attribute of SkelAnimation is invalid : {}", abs_path)); } + } - is_translations_timesamples = true; + + anim_out->blendshape_weights_map = std::move(weightsMap); } - const TypedTimeSamples> &ts_rots = rotations.get_timesamples(); - for (const auto &sample : ts_rots.get_samples()) { - if (!sample.blocked) { - if (sample.value.size() != joints.size()) { - PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} rotations.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path)); - } - } - } + anim_out->abs_path = abs_path.full_path_name(); + anim_out->prim_name = skelAnim.name; + anim_out->display_name = skelAnim.metas().displayName.value_or(""); - const TypedTimeSamples> &ts_scales = scales.get_timesamples(); - for (const auto &sample : ts_scales.get_samples()) { - if (!sample.blocked) { - if (sample.value.size() != joints.size()) { - PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} scales.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path)); - } - } - } - - std::vector translation; - std::vector rotation; - std::vector scale; - - // Get value and also do length check for scalar(non timeSampled) animation value. - if (translations.is_scalar()) { - if (!translations.get_scalar(&translation)) { - PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `translations` attribute in SkelAnimation: {}", abs_path)); - } - if (translation.size() != joints.size()) { - PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. translations.default.size {} must be equal to joints.size {} : {}", translation.size(), joints.size(), abs_path)); - } - is_translations_timesamples = false; - } - - if (rotations.is_scalar()) { - std::vector _rotation; - if (!rotations.get_scalar(&_rotation)) { - PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `rotations` attribute in SkelAnimation: {}", abs_path)); - } - if (_rotation.size() != joints.size()) { - PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. rotations.default.size {} must be equal to joints.size {} : {}", _rotation.size(), joints.size(), abs_path)); - } - std::transform(_rotation.begin(), _rotation.end(), std::back_inserter(rotation), [](const value::quatf &v) { - value::float4 ret; - // pxrUSD's TfQuat also uses xyzw memory order. - ret[0] = v[0]; - ret[1] = v[1]; - ret[2] = v[2]; - ret[3] = v[3]; - return ret; - }); - is_rotations_timesamples = false; - } - - if (scales.is_scalar()) { - std::vector _scale; - if (!scales.get_scalar(&_scale)) { - PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `scales` attribute in SkelAnimation: {}", abs_path)); - } - if (_scale.size() != joints.size()) { - PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. scale.default.size {} must be equal to joints.size {} : {}", _scale.size(), joints.size(), abs_path)); - } - // half -> float - std::transform(_scale.begin(), _scale.end(), std::back_inserter(scale), [](const value::half3 &v) { - value::float3 ret; - ret[0] = value::half_to_float(v[0]); - ret[1] = value::half_to_float(v[1]); - ret[2] = value::half_to_float(v[2]); - return ret; - }); - is_scales_timesamples = false; - } - - // Use USD TimeCode::Default for static sample. - if (is_translations_timesamples) { - } else { - AnimationSample> sample; - sample.t = std::numeric_limits::quiet_NaN(); - sample.value = translation; - channel_txs.translations.samples.push_back(sample); - } - - if (is_rotations_timesamples) { - } else { - AnimationSample> sample; - sample.t = std::numeric_limits::quiet_NaN(); - sample.value = rotation; - channel_rots.rotations.samples.push_back(sample); - } - - if (is_scales_timesamples) { - } else { - AnimationSample> sample; - sample.t = std::numeric_limits::quiet_NaN(); - sample.value = scale; - channel_scales.scales.samples.push_back(sample); - } - - PUSH_ERROR_AND_RETURN("TODO"); + anim_out->channels_map = std::move(channelMap); + return true; } bool RenderSceneConverter::BuildNodeHierarchyImpl( @@ -5435,6 +5706,7 @@ bool RenderSceneConverter::ConvertToRenderScene( // // 2. Convert Material/Texture // 3. Convert Mesh/SkinWeights/BlendShapes + // 4. Convert Skeleton(bones) and SkelAnimation // // Material conversion will be done in MeshVisitor. // @@ -5448,11 +5720,6 @@ bool RenderSceneConverter::ConvertToRenderScene( PUSH_ERROR_AND_RETURN(err); } - // - // 4. Convert Skeletons - // - // TODO - // // 5. Build node hierarchy from XformNode and meshes, materials, skeletons, // etc. @@ -5484,11 +5751,107 @@ bool RenderSceneConverter::ConvertToRenderScene( render_scene.buffers = std::move(buffers); render_scene.materials = std::move(materials); render_scene.skeletons = std::move(skeletons); + render_scene.animations = std::move(animations); (*scene) = std::move(render_scene); return true; } +bool RenderSceneConverter::ConvertSkeletonImpl(const RenderSceneConverterEnv &env, const tinyusdz::GeomMesh &mesh, + SkelHierarchy *out_skel, nonstd::optional *out_anim) { + + if (!out_skel) { + return false; + } + + if (!mesh.skeleton.has_value()) { + return false; + } + + Path skelPath; + + if (mesh.skeleton.value().is_path()) { + skelPath = mesh.skeleton.value().targetPath; + } else if (mesh.skeleton.value().is_pathvector()) { + // Use the first one + if (mesh.skeleton.value().targetPathVector.size()) { + skelPath = mesh.skeleton.value().targetPathVector[0]; + } else { + PUSH_WARN("`skel:skeleton` has invalid definition."); + } + } else { + PUSH_WARN("`skel:skeleton` has invalid definition."); + } + + if (skelPath.is_valid()) { + const Prim *skelPrim{nullptr}; + if (!env.stage.find_prim_at_path(skelPath, skelPrim, &_err)) { + return false; + } + + SkelHierarchy dst; + if (const auto pskel = skelPrim->as()) { + SkelNode root; + if (!BuildSkelHierarchy((*pskel), root, &_err)) { + return false; + } + dst.abs_path = skelPath.prim_part(); + dst.prim_name = skelPrim->element_name(); + dst.display_name = pskel->metas().displayName.value_or(""); + dst.root_node = root; + + if (pskel->animationSource.has_value()) { + DCOUT("skel:animationSource"); + + const Relationship &animSourceRel = pskel->animationSource.value(); + + Path animSourcePath; + + if (animSourceRel.is_path()) { + animSourcePath = animSourceRel.targetPath; + } else if (animSourceRel.is_pathvector()) { + // Use the first one + if (animSourceRel.targetPathVector.size()) { + animSourcePath = animSourceRel.targetPathVector[0]; + } else { + PUSH_ERROR_AND_RETURN("`skel:animationSource` has invalid definition."); + } + } else { + PUSH_ERROR_AND_RETURN("`skel:animationSource` has invalid definition."); + } + + const Prim *animSourcePrim{nullptr}; + if (!env.stage.find_prim_at_path(animSourcePath, animSourcePrim, &_err)) { + return false; + } + + if (const auto panim = animSourcePrim->as()) { + DCOUT("Convert SkelAnimation"); + Animation anim; + if (!ConvertSkelAnimation(env, animSourcePath, *panim, &anim)) { + return false; + } + + DCOUT("Converted SkelAnimation"); + (*out_anim) = anim; + + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Target Prim of `skel:animationSource` must be `SkelAnimation` Prim, but got `{}`.", animSourcePrim->prim_type_name())); + } + + + } + } else { + PUSH_ERROR_AND_RETURN("Prim is not Skeleton."); + } + + (*out_skel) = dst; + return true; + } + + PUSH_ERROR_AND_RETURN("`skel:skeleton` path is invalid."); +} + bool DefaultTextureImageLoaderFunction( const value::AssetPath &assetPath, const AssetInfo &assetInfo, const AssetResolutionResolver &assetResolver, TextureImage *texImageOut, @@ -6188,6 +6551,8 @@ std::string DumpMesh(const RenderMesh &mesh, uint32_t indent) { ss << pprint::Indent(indent + 1) << "}\n"; } + ss << pprint::Indent(indent + 1) << "skek_id " << mesh.skel_id << "\n"; + if (mesh.joint_and_weights.jointIndices.size()) { ss << pprint::Indent(indent + 1) << "skin {\n"; ss << pprint::Indent(indent + 2) << "geomBindTransform " @@ -6237,6 +6602,112 @@ std::string DumpMesh(const RenderMesh &mesh, uint32_t indent) { return ss.str(); } +namespace detail { + +void DumpSkelNode(std::stringstream &ss, const SkelNode &node, uint32_t indent) { + + ss << pprint::Indent(indent) << node.joint_name << " {\n"; + + ss << pprint::Indent(indent + 1) << "joint_path " << quote(node.joint_path) << "\n"; + ss << pprint::Indent(indent + 1) << "joint_id " << node.joint_id << "\n"; + ss << pprint::Indent(indent + 1) << "bind_transform " << quote(tinyusdz::to_string(node.bind_transform)) << "\n"; + ss << pprint::Indent(indent + 1) << "rest_transform " << quote(tinyusdz::to_string(node.rest_transform)) << "\n"; + + if (node.children.size()) { + ss << pprint::Indent(indent + 1) << "children {\n"; + for (const auto &child : node.children) { + DumpSkelNode(ss, child, indent + 2); + } + ss << pprint::Indent(indent + 1) << "}\n"; + } + + ss << pprint::Indent(indent) << "}\n"; +} + + +} // namespace detail + +std::string DumpSkeleton(const SkelHierarchy &skel, uint32_t indent) { + std::stringstream ss; + + ss << pprint::Indent(indent) << "skeleton {\n"; + + ss << pprint::Indent(indent + 1) << "name " << quote(skel.prim_name) << "\n"; + ss << pprint::Indent(indent + 1) << "abs_path " << quote(skel.abs_path) + << "\n"; + ss << pprint::Indent(indent + 1) << "display_name " + << quote(skel.display_name) << "\n"; + + detail::DumpSkelNode(ss, skel.root_node, indent + 1); + + ss << "\n"; + + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +namespace detail { + +template +std::string PrintAnimationSamples(const std::vector> &samples) { + std::stringstream ss; + + ss << "["; + for (size_t i = 0; i < samples.size(); i++) { + if (i > 0) { + ss << ", "; + } + + ss << "(" << samples[i].t << ", " << samples[i].value << ")"; + } + ss << "]"; + + return ss.str(); +} + +void DumpAnimChannel(std::stringstream &ss, const std::string &name, const std::map &channels, uint32_t indent) { + + ss << pprint::Indent(indent) << name << " {\n"; + + for (const auto &channel : channels) { + if (channel.first == AnimationChannel::ChannelType::Translation) { + ss << pprint::Indent(indent + 1) << "translations " << quote(detail::PrintAnimationSamples(channel.second.translations.samples)) << "\n"; + } else if (channel.first == AnimationChannel::ChannelType::Rotation) { + ss << pprint::Indent(indent + 1) << "rotations " << quote(detail::PrintAnimationSamples(channel.second.rotations.samples)) << "\n"; + } else if (channel.first == AnimationChannel::ChannelType::Scale) { + ss << pprint::Indent(indent + 1) << "scales " << quote(detail::PrintAnimationSamples(channel.second.scales.samples)) << "\n"; + } + } + + ss << pprint::Indent(indent) << "}\n"; +} + + +} // namespace detail + +std::string DumpAnimation(const Animation &anim, uint32_t indent) { + std::stringstream ss; + + ss << pprint::Indent(indent) << "animation {\n"; + + ss << pprint::Indent(indent + 1) << "name " << quote(anim.prim_name) << "\n"; + ss << pprint::Indent(indent + 1) << "abs_path " << quote(anim.abs_path) + << "\n"; + ss << pprint::Indent(indent + 1) << "display_name " + << quote(anim.display_name) << "\n"; + + for (const auto &channel : anim.channels_map) { + detail::DumpAnimChannel(ss, channel.first, channel.second, indent + 1); + } + + ss << "\n"; + + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + std::string DumpCamera(const RenderCamera &camera, uint32_t indent) { std::stringstream ss; @@ -6477,8 +6948,9 @@ std::string DumpRenderScene(const RenderScene &scene, ss << "default_root_node " << scene.default_root_node << "\n"; ss << "// # of Root Nodes : " << scene.nodes.size() << "\n"; ss << "// # of Meshes : " << scene.meshes.size() << "\n"; - ss << "// # of Cameras : " << scene.cameras.size() << "\n"; + ss << "// # of Skeletons : " << scene.skeletons.size() << "\n"; ss << "// # of Animations : " << scene.animations.size() << "\n"; + ss << "// # of Cameras : " << scene.cameras.size() << "\n"; ss << "// # of Materials : " << scene.materials.size() << "\n"; ss << "// # of UVTextures : " << scene.textures.size() << "\n"; ss << "// # of TextureImages : " << scene.images.size() << "\n"; @@ -6498,6 +6970,18 @@ std::string DumpRenderScene(const RenderScene &scene, } ss << "}\n"; + ss << "skeletons {\n"; + for (size_t i = 0; i < scene.skeletons.size(); i++) { + ss << "[" << i << "] " << DumpSkeleton(scene.skeletons[i], 1); + } + ss << "}\n"; + + ss << "animations {\n"; + for (size_t i = 0; i < scene.animations.size(); i++) { + ss << "[" << i << "] " << DumpAnimation(scene.animations[i], 1); + } + ss << "}\n"; + ss << "cameras {\n"; for (size_t i = 0; i < scene.cameras.size(); i++) { ss << "[" << i << "] " << DumpCamera(scene.cameras[i], 1); @@ -6532,7 +7016,7 @@ std::string DumpRenderScene(const RenderScene &scene, } ss << "}\n"; - // ss << "TODO: Animations, ...\n"; + // ss << "TODO: AnimationChannel, ...\n"; return ss.str(); } diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.hh b/contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.hh index a129b8296..2f1ee36de 100644 --- a/contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.hh +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.hh @@ -659,10 +659,17 @@ struct AnimationSampler { Interpolation interpolation{Interpolation::Linear}; }; -// TODO: Supprot more data types(e.g. float2) +// We store animation data in AoS(array of structure) approach(glTF-like), i.e. animation channel is provided per joint, instead of +// SoA(structure of array) approach(USD SkelAnimation) +// TODO: Use VertexAttribute-like data structure struct AnimationChannel { enum class ChannelType { Transform, Translation, Rotation, Scale, Weight }; + AnimationChannel() = default; + + AnimationChannel(ChannelType ty) : type(ty) { + } + ChannelType type; // The following AnimationSampler is filled depending on ChannelType. // Example: Rotation => Only `rotations` are filled. @@ -670,14 +677,15 @@ struct AnimationChannel { // Matrix precision is reduced to float-precision // NOTE: transform is not supported in glTF(you need to decompose transform // matrix into TRS) - AnimationSampler> transforms; + AnimationSampler transforms; - AnimationSampler> translations; - AnimationSampler> rotations; // Rotation is represented as quaternions - AnimationSampler> scales; // half-types are upcasted to float precision - AnimationSampler> weights; + AnimationSampler translations; + AnimationSampler rotations; // Rotation is represented as quaternions + AnimationSampler scales; // half-types are upcasted to float precision + AnimationSampler weights; - int64_t taget_node{-1}; // array index to RenderScene::nodes + //std::string joint_name; // joint name(UsdSkel::joints) + //int64_t joint_id{-1}; // joint index in SkelHierarchy }; // USD SkelAnimation @@ -685,7 +693,14 @@ struct Animation { std::string prim_name; // Prim name(element name) std::string abs_path; // Target USD Prim path std::string display_name; // `displayName` prim meta - std::vector channels; + + // key = joint, value = (key: channel_type, value: channel_value) + std::map> channels_map; + + // For blendshapes + // key = blendshape name, value = timesamped weights + // TODO: in-between weight + std::map>> blendshape_weights_map; }; struct Node { @@ -795,6 +810,7 @@ struct MaterialSubset { // Currently normals and texcoords are converted as facevarying attribute. struct RenderMesh { +#if 0 // deprecated. // // Type of Vertex attributes of this mesh. // @@ -810,14 +826,19 @@ struct RenderMesh { // Facevaring(no VertexArray indices). This would impact // rendering performance. }; +#endif std::string prim_name; // Prim name std::string abs_path; // Absolute Prim path in Stage std::string display_name; // `displayName` Prim metadataum - VertexArrayType vertexArrayType{VertexArrayType::Facevarying}; + // true: all vertex attributes are 'vertex'-varying. i.e, an App can simply use faceVertexIndices to draw mesh. + // false: some vertex attributes are 'facevarying'-varying. An app need to decompose 'points' and 'vertex'-varying attribute to 'facevarying' variability to draw a mesh. + bool is_single_indexable{false}; - std::vector points; // varying is 'vertex'. + //VertexArrayType vertexArrayType{VertexArrayType::Facevarying}; + + std::vector points; // varying is always 'vertex'. /// /// Initialized with USD faceVertexIndices/faceVertexCounts in GeomMesh. @@ -905,6 +926,7 @@ struct RenderMesh { // For vertex skinning JointAndWeight joint_and_weights; + int skel_id{-1}; // index to RenderScene::skeletons // BlendShapes // key = USD BlendShape prim name. @@ -938,6 +960,7 @@ enum class UVReaderFloatComponentType { std::string to_string(UVReaderFloatComponentType ty); +// TODO: Deprecate UVReaderFloat. // float, float2, float3 or float4 only struct UVReaderFloat { UVReaderFloatComponentType componentType{ @@ -945,11 +968,11 @@ struct UVReaderFloat { int64_t mesh_id{-1}; // index to RenderMesh int64_t coord_id{-1}; // index to RenderMesh::facevaryingTexcoords - // mat2 transform; // UsdTransform2d - +#if 0 // Returns interpolated UV coordinate with UV transform // # of components filled are equal to `componentType`. vec4 fetchUV(size_t faceId, float varyu, float varyv); +#endif }; struct UVTexture { @@ -1590,6 +1613,7 @@ class RenderSceneConverter { StringAndIdMap textureMap; StringAndIdMap imageMap; StringAndIdMap bufferMap; + StringAndIdMap animationMap; int default_node{-1}; @@ -1602,6 +1626,7 @@ class RenderSceneConverter { std::vector images; std::vector buffers; std::vector skeletons; + std::vector animations; /// /// Convert GeomMesh to renderer-friendly mesh. @@ -1716,8 +1741,8 @@ class RenderSceneConverter { /// Convert SkelAnimation to Tydra Animation. /// /// @param[in] abs_path USD Path to SkelAnimation Prim - /// @param[in] skelAnim SkelAnimation - /// @param[in] anim_out UVTexture + /// @param[in] skelAnim SkelAnimatio + /// @param[in] anim_out Animation /// bool ConvertSkelAnimation(const RenderSceneConverterEnv &env, const Path &abs_path, const SkelAnimation &skelAnim, @@ -1764,10 +1789,11 @@ class RenderSceneConverter { bool BuildVertexIndicesImpl(RenderMesh &mesh); // - // Get Skeleton assigned to this GeomMesh Prim + // Get Skeleton assigned to the GeomMesh Prim and convert it to SkelHierarchy. + // Also get SkelAnimation attached to Skeleton(if exists) // - bool GetSkeletonImpl(const tinyusdz::Prim &prim, - const tinyusdz::Skeleton *&out_skeleton); + bool ConvertSkeletonImpl(const RenderSceneConverterEnv &env, const tinyusdz::GeomMesh &mesh, + SkelHierarchy *out_skel, nonstd::optional *out_anim); bool BuildNodeHierarchyImpl( const RenderSceneConverterEnv &env, diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.cc b/contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.cc index 220ef13ab..b12f86ea9 100644 --- a/contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.cc +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.cc @@ -17,10 +17,8 @@ #include "value-pprint.hh" // src/tydra -#include "scene-access.hh" #include "attribute-eval.hh" - -#include "../../../assimp_tinyusdz_logging.inc" +#include "scene-access.hh" namespace tinyusdz { namespace tydra { @@ -177,13 +175,15 @@ bool ListShaders(const tinyusdz::Stage &stage, PathShaderMap &m /* output */) { // Concrete Shader type(e.g. UsdPreviewSurface) is classified as Imaging // Should report error at compilation stege. - static_assert( - (value::TypeId::TYPE_ID_IMAGING_BEGIN <= value::TypeTraits::type_id()) && - (value::TypeId::TYPE_ID_IMAGING_END > value::TypeTraits::type_id()), - "Not a Shader type."); + static_assert((value::TypeId::TYPE_ID_IMAGING_BEGIN <= + value::TypeTraits::type_id()) && + (value::TypeId::TYPE_ID_IMAGING_END > + value::TypeTraits::type_id()), + "Not a Shader type."); // Check at runtime. Just in case... - if ((value::TypeId::TYPE_ID_IMAGING_BEGIN <= value::TypeTraits::type_id()) && + if ((value::TypeId::TYPE_ID_IMAGING_BEGIN <= + value::TypeTraits::type_id()) && (value::TypeId::TYPE_ID_IMAGING_END > value::TypeTraits::type_id())) { // Ok } else { @@ -246,7 +246,7 @@ const Prim *GetParentPrim(const tinyusdz::Stage &stage, // Template Instanciations // #define LISTPRIMS_INSTANCIATE(__ty) \ -template bool ListPrims(const tinyusdz::Stage &stage, PathPrimMap<__ty> &m); + template bool ListPrims(const tinyusdz::Stage &stage, PathPrimMap<__ty> &m); APPLY_FUNC_TO_PRIM_TYPES(LISTPRIMS_INSTANCIATE) @@ -274,9 +274,10 @@ template bool ListShaders(const tinyusdz::Stage &stage, namespace { -bool VisitPrimsRec(const tinyusdz::Path &root_abs_path, const tinyusdz::Prim &root, int32_t level, - VisitPrimFunction visitor_fun, void *userdata, std::string *err) { - +bool VisitPrimsRec(const tinyusdz::Path &root_abs_path, + const tinyusdz::Prim &root, int32_t level, + VisitPrimFunction visitor_fun, void *userdata, + std::string *err) { std::string fun_error; bool ret = visitor_fun(root_abs_path, root, level, userdata, &fun_error); if (!ret) { @@ -285,7 +286,9 @@ bool VisitPrimsRec(const tinyusdz::Path &root_abs_path, const tinyusdz::Prim &ro DCOUT("Early termination requested"); } else { if (err) { - (*err) += fmt::format("Visit function returned an error for Prim {} (id {}). err = {}", root_abs_path.full_path_name(), root.prim_id(), fun_error); + (*err) += fmt::format( + "Visit function returned an error for Prim {} (id {}). err = {}", + root_abs_path.full_path_name(), root.prim_id(), fun_error); } } return false; @@ -295,7 +298,8 @@ bool VisitPrimsRec(const tinyusdz::Path &root_abs_path, const tinyusdz::Prim &ro if (root.metas().primChildren.size() == root.children().size()) { std::map primNameTable; for (size_t i = 0; i < root.children().size(); i++) { - primNameTable.emplace(root.children()[i].element_name(), &root.children()[i]); + primNameTable.emplace(root.children()[i].element_name(), + &root.children()[i]); } for (size_t i = 0; i < root.metas().primChildren.size(); i++) { @@ -303,26 +307,30 @@ bool VisitPrimsRec(const tinyusdz::Path &root_abs_path, const tinyusdz::Prim &ro const auto it = primNameTable.find(nameTok.str()); if (it != primNameTable.end()) { const Path child_abs_path = root_abs_path.AppendPrim(nameTok.str()); - if (!VisitPrimsRec(child_abs_path, *it->second, level + 1, visitor_fun, userdata, err)) { + if (!VisitPrimsRec(child_abs_path, *it->second, level + 1, visitor_fun, + userdata, err)) { return false; } } else { if (err) { - (*err) += fmt::format("Prim name `{}` in `primChildren` metadatum not found in this Prim's children", nameTok.str()); + (*err) += fmt::format( + "Prim name `{}` in `primChildren` metadatum not found in this " + "Prim's children", + nameTok.str()); } return false; } } } else { - for (const auto &child : root.children()) { - const Path child_abs_path = root_abs_path.AppendPrim(child.element_name()); - if (!VisitPrimsRec(child_abs_path, child, level + 1, visitor_fun, userdata, err)) { + const Path child_abs_path = + root_abs_path.AppendPrim(child.element_name()); + if (!VisitPrimsRec(child_abs_path, child, level + 1, visitor_fun, + userdata, err)) { return false; } } - } return true; @@ -372,7 +380,8 @@ void ToProperty( // Scalar-valued attribute. // TypedAttribute* => Attribute defined in USD schema, so not a custom attr. template -bool ToProperty(const TypedAttribute &input, Property &output, std::string *err) { +bool ToProperty(const TypedAttribute &input, Property &output, + std::string *err) { if (input.is_blocked()) { Attribute attr; attr.set_blocked(input.is_blocked()); @@ -381,7 +390,8 @@ bool ToProperty(const TypedAttribute &input, Property &output, std::string *e output = Property(std::move(attr), /*custom*/ false); } else if (input.is_value_empty()) { // type info only - output = Property::MakeEmptyAttrib(value::TypeTraits::type_name(), /* custom */ false); + output = Property::MakeEmptyAttrib(value::TypeTraits::type_name(), + /* custom */ false); } else if (input.is_connection()) { // Use Relation for Connection(as typed relationshipTarget) // Single connection targetPath only. @@ -389,7 +399,8 @@ bool ToProperty(const TypedAttribute &input, Property &output, std::string *e std::vector paths = input.get_connections(); if (paths.empty()) { if (err) { - (*err) += fmt::format("[InternalError] Connection attribute but empty targetPaths."); + (*err) += fmt::format( + "[InternalError] Connection attribute but empty targetPaths."); } return false; } else if (paths.size() == 1) { @@ -412,7 +423,9 @@ bool ToProperty(const TypedAttribute &input, Property &output, std::string *e output = Property(attr, /* custom */ false); } else { if (err) { - (*err) += fmt::format("[InternalError] Invalid TypedAttribute<{}> value.", value::TypeTraits::type_name()); + (*err) += + fmt::format("[InternalError] Invalid TypedAttribute<{}> value.", + value::TypeTraits::type_name()); } return false; @@ -427,7 +440,8 @@ bool ToProperty(const TypedAttribute &input, Property &output, std::string *e // // TODO: Support timeSampled attribute. template -bool ToProperty(const TypedAttribute> &input, Property &output, std::string *err) { +bool ToProperty(const TypedAttribute> &input, Property &output, + std::string *err) { if (input.is_blocked()) { DCOUT("Value is blocked"); Attribute attr; @@ -439,7 +453,8 @@ bool ToProperty(const TypedAttribute> &input, Property &output, st } else if (input.is_value_empty()) { DCOUT("Value is empty"); // type info only - output = Property::MakeEmptyAttrib(value::TypeTraits::type_name(), /* custom */ false); + output = Property::MakeEmptyAttrib(value::TypeTraits::type_name(), + /* custom */ false); return true; } else if (input.is_connection()) { DCOUT("IsConnection"); @@ -451,7 +466,8 @@ bool ToProperty(const TypedAttribute> &input, Property &output, st DCOUT("??? Empty connectionTarget."); if (err) { - (*err) += fmt::format("[InternalError] Connection attribute but empty targetPaths."); + (*err) += fmt::format( + "[InternalError] Connection attribute but empty targetPaths."); } return false; } @@ -466,7 +482,10 @@ bool ToProperty(const TypedAttribute> &input, Property &output, st } else { DCOUT("??? GetConnection failue."); if (err) { - (*err) += fmt::format("[InternalError] get_connection() to TypedAttribute> failed.", value::TypeTraits::type_name()); + (*err) += fmt::format( + "[InternalError] get_connection() to " + "TypedAttribute> failed.", + value::TypeTraits::type_name()); } return false; } @@ -484,13 +503,13 @@ bool ToProperty(const TypedAttribute> &input, Property &output, st T a; if (aval.value().get_scalar(&a)) { DCOUT("Value get ok"); - //value::Value val(a); + // value::Value val(a); Attribute attr; - //attr.set_type_name(value::TypeTraits::type_name()); + // attr.set_type_name(value::TypeTraits::type_name()); primvar::PrimVar pvar; pvar.set_value(a); attr.set_var(std::move(pvar)); - //attr.set_value(val); + // attr.set_value(val); attr.variability() = Variability::Varying; output = Property(attr, /* custom */ false); return true; @@ -508,7 +527,10 @@ bool ToProperty(const TypedAttribute> &input, Property &output, st } else if (aval.value().is_timesamples()) { DCOUT("TODO: Convert typed TimeSamples to value::TimeSamples"); if (err) { - (*err) += fmt::format("[TODO] TimeSamples value of TypedAttribute> is not yet implemented.", value::TypeTraits::type_name()); + (*err) += fmt::format( + "[TODO] TimeSamples value of TypedAttribute> is " + "not yet implemented.", + value::TypeTraits::type_name()); } return false; } @@ -771,7 +793,8 @@ bool ToTokenProperty(const TypedAttributeWithFallback &input, } template -nonstd::optional TypedTerminalAttributeToProperty(const TypedTerminalAttribute &input) { +nonstd::optional TypedTerminalAttributeToProperty( + const TypedTerminalAttribute &input) { if (!input.authored()) { // nothing to do return nonstd::nullopt; @@ -782,7 +805,8 @@ nonstd::optional TypedTerminalAttributeToProperty(const TypedTerminalA // type info only if (input.has_actual_type()) { // type info only - output = Property::MakeEmptyAttrib(input.get_actual_type_name(), /* custom */ false); + output = Property::MakeEmptyAttrib(input.get_actual_type_name(), + /* custom */ false); } else { output = Property::MakeEmptyAttrib(input.type_name(), /* custom */ false); } @@ -790,7 +814,6 @@ nonstd::optional TypedTerminalAttributeToProperty(const TypedTerminalA return output; } - bool XformOpToProperty(const XformOp &x, Property &prop) { primvar::PrimVar pv; @@ -826,27 +849,29 @@ bool XformOpToProperty(const XformOp &x, Property &prop) { return true; } -#define TO_PROPERTY(__prop_name, __v) \ - if (prop_name == __prop_name) { \ - if (!ToProperty(__v, *out_prop, &err)) { \ - return nonstd::make_unexpected(fmt::format("Convert Property {} failed: {}\n", __prop_name, err)); \ - } \ +#define TO_PROPERTY(__prop_name, __v) \ + if (prop_name == __prop_name) { \ + if (!ToProperty(__v, *out_prop, &err)) { \ + return nonstd::make_unexpected( \ + fmt::format("Convert Property {} failed: {}\n", __prop_name, err)); \ + } \ } else -#define TO_TOKEN_PROPERTY(__prop_name, __v) \ - if (prop_name == __prop_name) { \ - if (!ToTokenProperty(__v, *out_prop, &err)) { \ - return nonstd::make_unexpected(fmt::format("Convert Property {} failed: {}\n", __prop_name, err)); \ - } \ +#define TO_TOKEN_PROPERTY(__prop_name, __v) \ + if (prop_name == __prop_name) { \ + if (!ToTokenProperty(__v, *out_prop, &err)) { \ + return nonstd::make_unexpected( \ + fmt::format("Convert Property {} failed: {}\n", __prop_name, err)); \ + } \ } else // Return false: something went wrong // `attr_prop` true: Include Attribute property. // `rel_prop` true: Include Relationship property. template -bool GetPrimPropertyNamesImpl( - const T &prim, std::vector *prop_names, bool attr_prop=true, bool rel_prop=true); - +bool GetPrimPropertyNamesImpl(const T &prim, + std::vector *prop_names, + bool attr_prop = true, bool rel_prop = true); // Return true: Property found(`out_prop` filled) // Return false: Property not found @@ -964,7 +989,8 @@ nonstd::expected GetPrimProperty( TO_PROPERTY("holeIndices", mesh.holeIndices) TO_TOKEN_PROPERTY("interpolateBoundary", mesh.interpolateBoundary) TO_TOKEN_PROPERTY("subdivisionScheme", mesh.subdivisionScheme) - TO_TOKEN_PROPERTY("faceVaryingLinearInterpolation", mesh.faceVaryingLinearInterpolation) + TO_TOKEN_PROPERTY("faceVaryingLinearInterpolation", + mesh.faceVaryingLinearInterpolation) if (prop_name == "skeleton") { if (mesh.skeleton) { @@ -1005,13 +1031,13 @@ nonstd::expected GetPrimProperty( DCOUT("prop_name = " << prop_name); TO_PROPERTY("indices", subset.indices); TO_TOKEN_PROPERTY("elementType", subset.elementType); - //TO_TOKEN_PROPERTY("familyType", subset.familyType); + // TO_TOKEN_PROPERTY("familyType", subset.familyType); TO_PROPERTY("familyName", subset.familyName); if (prop_name == "material:binding") { if (subset.materialBinding) { const Relationship &rel = subset.materialBinding.value(); - (*out_prop) = Property(rel, /* custom */false); + (*out_prop) = Property(rel, /* custom */ false); } else { return false; } @@ -1068,8 +1094,7 @@ nonstd::expected GetPrimProperty( DCOUT("prop_name = " << prop_name); std::string err; - TO_PROPERTY("inputs:varname", preader.varname) - { + TO_PROPERTY("inputs:varname", preader.varname) { const auto it = preader.props.find(prop_name); if (it == preader.props.end()) { // Attribute not found. @@ -1189,7 +1214,9 @@ nonstd::expected GetPrimProperty( } // empty. type info only - std::string typeName = tx.result.has_actual_type() ? tx.result.get_actual_type_name() : tx.result.type_name(); + std::string typeName = tx.result.has_actual_type() + ? tx.result.get_actual_type_name() + : tx.result.type_name(); (*out_prop) = Property::MakeEmptyAttrib(typeName, /* custom */ false); } else { const auto it = tx.props.find(prop_name); @@ -1233,8 +1260,9 @@ nonstd::expected GetPrimProperty( if (prop_name == "outputs:surface") { if (surface.outputsSurface.authored()) { - // empty. type info only - (*out_prop) = Property::MakeEmptyAttrib(value::kToken, /* custom */ false); + // empty. type info only + (*out_prop) = + Property::MakeEmptyAttrib(value::kToken, /* custom */ false); } else { // Not authored return false; @@ -1242,7 +1270,8 @@ nonstd::expected GetPrimProperty( } else if (prop_name == "outputs:displacement") { if (surface.outputsDisplacement.authored()) { // empty. type info only - (*out_prop) = Property::MakeEmptyAttrib(value::kToken, /* custom */ false); + (*out_prop) = + Property::MakeEmptyAttrib(value::kToken, /* custom */ false); } else { // Not authored return false; @@ -1279,8 +1308,7 @@ nonstd::expected GetPrimProperty( attr.set_type_name(value::TypeTraits::type_name()); attr.set_connections(material.surface.get_connections()); attr.metas() = material.surface.metas(); - (*out_prop) = - Property(attr, /* custom */ false); + (*out_prop) = Property(attr, /* custom */ false); out_prop->set_listedit_qual(material.surface.get_listedit_qual()); } else { // Not authored @@ -1292,8 +1320,7 @@ nonstd::expected GetPrimProperty( attr.set_type_name(value::TypeTraits::type_name()); attr.set_connections(material.displacement.get_connections()); attr.metas() = material.displacement.metas(); - (*out_prop) = - Property(attr, /* custom */ false); + (*out_prop) = Property(attr, /* custom */ false); out_prop->set_listedit_qual(material.displacement.get_listedit_qual()); } else { // Not authored @@ -1305,8 +1332,7 @@ nonstd::expected GetPrimProperty( attr.set_type_name(value::TypeTraits::type_name()); attr.set_connections(material.volume.get_connections()); attr.metas() = material.volume.metas(); - (*out_prop) = - Property(attr, /* custom */ false); + (*out_prop) = Property(attr, /* custom */ false); out_prop->set_listedit_qual(material.volume.get_listedit_qual()); } else { // Not authored @@ -1484,9 +1510,9 @@ nonstd::expected GetPrimProperty( } template <> -bool GetPrimPropertyNamesImpl( - const Model &model, std::vector *prop_names, bool attr_prop, bool rel_prop) { - +bool GetPrimPropertyNamesImpl(const Model &model, + std::vector *prop_names, + bool attr_prop, bool rel_prop) { if (!prop_names) { return false; } @@ -1497,7 +1523,7 @@ bool GetPrimPropertyNamesImpl( if (rel_prop) { prop_names->push_back(prop.first); } - } else { // assume attribute + } else { // assume attribute if (attr_prop) { prop_names->push_back(prop.first); } @@ -1508,8 +1534,9 @@ bool GetPrimPropertyNamesImpl( } template <> -bool GetPrimPropertyNamesImpl( - const Scope &scope, std::vector *prop_names, bool attr_prop, bool rel_prop) { +bool GetPrimPropertyNamesImpl(const Scope &scope, + std::vector *prop_names, + bool attr_prop, bool rel_prop) { if (!prop_names) { return false; } @@ -1520,7 +1547,7 @@ bool GetPrimPropertyNamesImpl( if (rel_prop) { prop_names->push_back(prop.first); } - } else { // assume attribute + } else { // assume attribute if (attr_prop) { prop_names->push_back(prop.first); } @@ -1530,8 +1557,9 @@ bool GetPrimPropertyNamesImpl( return true; } -bool GetGPrimPropertyNamesImpl( - const GPrim *gprim, std::vector *prop_names, bool attr_prop, bool rel_prop) { +bool GetGPrimPropertyNamesImpl(const GPrim *gprim, + std::vector *prop_names, + bool attr_prop, bool rel_prop) { if (!gprim) { return false; } @@ -1541,7 +1569,6 @@ bool GetGPrimPropertyNamesImpl( } if (attr_prop) { - if (gprim->doubleSided.authored()) { prop_names->push_back("doubleSided"); } @@ -1577,7 +1604,6 @@ bool GetGPrimPropertyNamesImpl( } if (rel_prop) { - if (gprim->materialBinding) { prop_names->push_back(kMaterialBinding); } @@ -1606,7 +1632,8 @@ bool GetGPrimPropertyNamesImpl( if (collection.first.empty()) { rel_name = kMaterialBindingCollection + purpose_name; } else { - rel_name = kMaterialBindingCollection + std::string(":") + coll_name + purpose_name; + rel_name = kMaterialBindingCollection + std::string(":") + coll_name + + purpose_name; } prop_names->push_back(rel_name); @@ -1616,8 +1643,6 @@ bool GetGPrimPropertyNamesImpl( if (gprim->proxyPrim.authored()) { prop_names->push_back("proxyPrim"); } - - } // other props @@ -1626,7 +1651,7 @@ bool GetGPrimPropertyNamesImpl( if (rel_prop) { prop_names->push_back(prop.first); } - } else { // assume attribute + } else { // assume attribute if (attr_prop) { prop_names->push_back(prop.first); } @@ -1637,8 +1662,9 @@ bool GetGPrimPropertyNamesImpl( } template <> -bool GetPrimPropertyNamesImpl( - const Xform &xform, std::vector *prop_names, bool attr_prop, bool rel_prop) { +bool GetPrimPropertyNamesImpl(const Xform &xform, + std::vector *prop_names, + bool attr_prop, bool rel_prop) { if (!prop_names) { return false; } @@ -1647,8 +1673,9 @@ bool GetPrimPropertyNamesImpl( } template <> -bool GetPrimPropertyNamesImpl( - const GeomMesh &mesh, std::vector *prop_names, bool attr_prop, bool rel_prop) { +bool GetPrimPropertyNamesImpl(const GeomMesh &mesh, + std::vector *prop_names, + bool attr_prop, bool rel_prop) { if (!prop_names) { return false; } @@ -1673,9 +1700,9 @@ bool GetPrimPropertyNamesImpl( } template <> -bool GetPrimPropertyNamesImpl( - const GeomSubset &subset, std::vector *prop_names, bool attr_prop, bool rel_prop) { - +bool GetPrimPropertyNamesImpl(const GeomSubset &subset, + std::vector *prop_names, + bool attr_prop, bool rel_prop) { (void)rel_prop; if (!prop_names) { @@ -1704,17 +1731,16 @@ bool GetPrimPropertyNamesImpl( #undef TO_PROPERTY #undef TO_TOKEN_PROPERTY - } // namespace bool VisitPrims(const tinyusdz::Stage &stage, VisitPrimFunction visitor_fun, void *userdata, std::string *err) { - // if `primChildren` is available, use it if (stage.metas().primChildren.size() == stage.root_prims().size()) { std::map primNameTable; for (size_t i = 0; i < stage.root_prims().size(); i++) { - primNameTable.emplace(stage.root_prims()[i].element_name(), &stage.root_prims()[i]); + primNameTable.emplace(stage.root_prims()[i].element_name(), + &stage.root_prims()[i]); } for (size_t i = 0; i < stage.metas().primChildren.size(); i++) { @@ -1722,12 +1748,16 @@ bool VisitPrims(const tinyusdz::Stage &stage, VisitPrimFunction visitor_fun, const auto it = primNameTable.find(nameTok.str()); if (it != primNameTable.end()) { const Path root_abs_path("/" + nameTok.str(), ""); - if (!VisitPrimsRec(root_abs_path, *it->second, 0, visitor_fun, userdata, err)) { + if (!VisitPrimsRec(root_abs_path, *it->second, 0, visitor_fun, userdata, + err)) { return false; } } else { if (err) { - (*err) += fmt::format("Prim name `{}` in root Layer's `primChildren` metadatum not found in Layer root.", nameTok.str()); + (*err) += fmt::format( + "Prim name `{}` in root Layer's `primChildren` metadatum not " + "found in Layer root.", + nameTok.str()); } return false; } @@ -1735,8 +1765,9 @@ bool VisitPrims(const tinyusdz::Stage &stage, VisitPrimFunction visitor_fun, } else { for (const auto &root : stage.root_prims()) { - const Path root_abs_path("/" + root.element_name(), /* prop part */""); - if (!VisitPrimsRec(root_abs_path, root, /* root level */ 0, visitor_fun, userdata, err)) { + const Path root_abs_path("/" + root.element_name(), /* prop part */ ""); + if (!VisitPrimsRec(root_abs_path, root, /* root level */ 0, visitor_fun, + userdata, err)) { return false; } } @@ -1781,13 +1812,18 @@ bool GetProperty(const tinyusdz::Prim &prim, const std::string &attr_name, return true; } -bool GetPropertyNames(const tinyusdz::Prim &prim, std::vector *out_prop_names, std::string *err) { -#define GET_PRIM_PROPERTY_NAMES(__ty) \ - if (prim.is<__ty>()) { \ - auto ret = GetPrimPropertyNamesImpl(*prim.as<__ty>(), out_prop_names, true, true); \ - if (!ret) { \ - PUSH_ERROR_AND_RETURN(fmt::format("Failed to list up Property names of Prim type {}", value::TypeTraits<__ty>::type_name())); \ - } \ +bool GetPropertyNames(const tinyusdz::Prim &prim, + std::vector *out_prop_names, + std::string *err) { +#define GET_PRIM_PROPERTY_NAMES(__ty) \ + if (prim.is<__ty>()) { \ + auto ret = GetPrimPropertyNamesImpl(*prim.as<__ty>(), out_prop_names, \ + true, true); \ + if (!ret) { \ + PUSH_ERROR_AND_RETURN( \ + fmt::format("Failed to list up Property names of Prim type {}", \ + value::TypeTraits<__ty>::type_name())); \ + } \ } else GET_PRIM_PROPERTY_NAMES(Model) @@ -1796,12 +1832,12 @@ bool GetPropertyNames(const tinyusdz::Prim &prim, std::vector *out_ GET_PRIM_PROPERTY_NAMES(GeomMesh) GET_PRIM_PROPERTY_NAMES(GeomSubset) // TODO - //GET_PRIM_PROPERTY_NAMES(Shader) - //GET_PRIM_PROPERTY_NAMES(Material) - //GET_PRIM_PROPERTY_NAMES(SkelRoot) - //GET_PRIM_PROPERTY_NAMES(BlendShape) - //GET_PRIM_PROPERTY_NAMES(Skeleton) - //GET_PRIM_PROPERTY_NAMES(SkelAnimation) + // GET_PRIM_PROPERTY_NAMES(Shader) + // GET_PRIM_PROPERTY_NAMES(Material) + // GET_PRIM_PROPERTY_NAMES(SkelRoot) + // GET_PRIM_PROPERTY_NAMES(BlendShape) + // GET_PRIM_PROPERTY_NAMES(Skeleton) + // GET_PRIM_PROPERTY_NAMES(SkelAnimation) { PUSH_ERROR_AND_RETURN("TODO: Prim type " << prim.type_name()); } @@ -1811,26 +1847,31 @@ bool GetPropertyNames(const tinyusdz::Prim &prim, std::vector *out_ return true; } -bool GetRelationshipNames(const tinyusdz::Prim &prim, std::vector *out_rel_names, std::string *err) { -#define GET_PRIM_RELATIONSHIP_NAMES(__ty) \ - if (prim.is<__ty>()) { \ - auto ret = GetPrimPropertyNamesImpl(*prim.as<__ty>(), out_rel_names, false, true); \ - if (!ret) { \ - PUSH_ERROR_AND_RETURN(fmt::format("Failed to list up Property names of Prim type {}", value::TypeTraits<__ty>::type_name())); \ - } \ +bool GetRelationshipNames(const tinyusdz::Prim &prim, + std::vector *out_rel_names, + std::string *err) { +#define GET_PRIM_RELATIONSHIP_NAMES(__ty) \ + if (prim.is<__ty>()) { \ + auto ret = GetPrimPropertyNamesImpl(*prim.as<__ty>(), out_rel_names, \ + false, true); \ + if (!ret) { \ + PUSH_ERROR_AND_RETURN( \ + fmt::format("Failed to list up Property names of Prim type {}", \ + value::TypeTraits<__ty>::type_name())); \ + } \ } else GET_PRIM_RELATIONSHIP_NAMES(Model) GET_PRIM_RELATIONSHIP_NAMES(Xform) GET_PRIM_RELATIONSHIP_NAMES(Scope) GET_PRIM_RELATIONSHIP_NAMES(GeomMesh) - //GET_PRIM_RELATIONSHIP_NAMES(GeomSubset) - //GET_PRIM_RELATIONSHIP_NAMES(Shader) - //GET_PRIM_RELATIONSHIP_NAMES(Material) - //GET_PRIM_RELATIONSHIP_NAMES(SkelRoot) - //GET_PRIM_RELATIONSHIP_NAMES(BlendShape) - //GET_PRIM_RELATIONSHIP_NAMES(Skeleton) - //GET_PRIM_RELATIONSHIP_NAMES(SkelAnimation) + // GET_PRIM_RELATIONSHIP_NAMES(GeomSubset) + // GET_PRIM_RELATIONSHIP_NAMES(Shader) + // GET_PRIM_RELATIONSHIP_NAMES(Material) + // GET_PRIM_RELATIONSHIP_NAMES(SkelRoot) + // GET_PRIM_RELATIONSHIP_NAMES(BlendShape) + // GET_PRIM_RELATIONSHIP_NAMES(Skeleton) + // GET_PRIM_RELATIONSHIP_NAMES(SkelAnimation) { PUSH_ERROR_AND_RETURN("TODO: Prim type " << prim.type_name()); } @@ -1840,7 +1881,6 @@ bool GetRelationshipNames(const tinyusdz::Prim &prim, std::vector * return true; } - bool GetAttribute(const tinyusdz::Prim &prim, const std::string &attr_name, Attribute *out_attr, std::string *err) { if (!out_attr) { @@ -1912,13 +1952,10 @@ bool ListSceneNames(const tinyusdz::Prim &root, namespace { bool BuildXformNodeFromStageRec( - const tinyusdz::Stage &stage, - const Path &parent_abs_path, - const Prim *prim, - XformNode *nodeOut, /* out */ - value::matrix4d rootMat, - const double t, const tinyusdz::value::TimeSampleInterpolationType tinterp) { - + const tinyusdz::Stage &stage, const Path &parent_abs_path, const Prim *prim, + XformNode *nodeOut, /* out */ + value::matrix4d rootMat, const double t, + const tinyusdz::value::TimeSampleInterpolationType tinterp) { if (!nodeOut) { return false; } @@ -1932,14 +1969,15 @@ bool BuildXformNodeFromStageRec( node.element_name = prim->element_name(); node.absolute_path = parent_abs_path.AppendPrim(prim->element_name()); node.prim_id = prim->prim_id(); - node.prim = prim; // Assume Prim's address does not change. + node.prim = prim; // Assume Prim's address does not change. DCOUT(prim->element_name() << ": IsXformablePrim" << IsXformablePrim(*prim)); if (IsXformablePrim(*prim)) { bool resetXformStack{false}; - value::matrix4d localMat = GetLocalTransform(*prim, &resetXformStack, t, tinterp); + value::matrix4d localMat = + GetLocalTransform(*prim, &resetXformStack, t, tinterp); DCOUT("local mat = " << localMat); value::matrix4d worldMat = rootMat; @@ -1970,7 +2008,9 @@ bool BuildXformNodeFromStageRec( for (const auto &childPrim : prim->children()) { XformNode childNode; - if (!BuildXformNodeFromStageRec(stage, node.absolute_path, &childPrim, &childNode, node.get_world_matrix(), t, tinterp)) { + if (!BuildXformNodeFromStageRec(stage, node.absolute_path, &childPrim, + &childNode, node.get_world_matrix(), t, + tinterp)) { return false; } @@ -1980,47 +2020,47 @@ bool BuildXformNodeFromStageRec( (*nodeOut) = node; - return true; } -std::string DumpXformNodeRec( - const XformNode &node, - uint32_t indent) -{ +std::string DumpXformNodeRec(const XformNode &node, uint32_t indent) { std::stringstream ss; - ss << pprint::Indent(indent) << "Prim name: " << node.element_name << " PrimID: " << node.prim_id << " (Path " << node.absolute_path << ") Xformable? " << node.has_xform() << " resetXformStack? " << node.has_resetXformStack() << " {\n"; - ss << pprint::Indent(indent+1) << "parent_world: " << node.get_parent_world_matrix() << "\n"; - ss << pprint::Indent(indent+1) << "world: " << node.get_world_matrix() << "\n"; - ss << pprint::Indent(indent+1) << "local: " << node.get_local_matrix() << "\n"; + ss << pprint::Indent(indent) << "Prim name: " << node.element_name + << " PrimID: " << node.prim_id << " (Path " << node.absolute_path + << ") Xformable? " << node.has_xform() << " resetXformStack? " + << node.has_resetXformStack() << " {\n"; + ss << pprint::Indent(indent + 1) + << "parent_world: " << node.get_parent_world_matrix() << "\n"; + ss << pprint::Indent(indent + 1) << "world: " << node.get_world_matrix() + << "\n"; + ss << pprint::Indent(indent + 1) << "local: " << node.get_local_matrix() + << "\n"; for (const auto &child : node.children) { - ss << DumpXformNodeRec(child, indent+1); + ss << DumpXformNodeRec(child, indent + 1); } - ss << pprint::Indent(indent+1) << "}\n"; + ss << pprint::Indent(indent + 1) << "}\n"; return ss.str(); } - -} // namespace local +} // namespace local bool BuildXformNodeFromStage( - const tinyusdz::Stage &stage, - XformNode *rootNode, /* out */ - const double t, const tinyusdz::value::TimeSampleInterpolationType tinterp) { - + const tinyusdz::Stage &stage, XformNode *rootNode, /* out */ + const double t, + const tinyusdz::value::TimeSampleInterpolationType tinterp) { if (!rootNode) { return false; } XformNode stage_root; - stage_root.element_name = ""; // Stage root element name is empty. + stage_root.element_name = ""; // Stage root element name is empty. stage_root.absolute_path = Path("/", ""); stage_root.has_xform() = false; stage_root.parent = nullptr; - stage_root.prim = nullptr; // No prim for stage root. + stage_root.prim = nullptr; // No prim for stage root. stage_root.prim_id = -1; stage_root.has_resetXformStack() = false; stage_root.set_parent_world_matrix(value::matrix4d::identity()); @@ -2032,7 +2072,8 @@ bool BuildXformNodeFromStage( value::matrix4d rootMat{value::matrix4d::identity()}; - if (!BuildXformNodeFromStageRec(stage, stage_root.absolute_path, &root, &node, rootMat, t, tinterp)) { + if (!BuildXformNodeFromStageRec(stage, stage_root.absolute_path, &root, + &node, rootMat, t, tinterp)) { return false; } @@ -2044,17 +2085,14 @@ bool BuildXformNodeFromStage( return true; } -std::string DumpXformNode( - const XformNode &node) -{ +std::string DumpXformNode(const XformNode &node) { return DumpXformNodeRec(node, 0); - } -template +template bool PrimToPrimSpecImpl(const T &p, PrimSpec &ps, std::string *err); -template<> +template <> bool PrimToPrimSpecImpl(const Model &p, PrimSpec &ps, std::string *err) { (void)err; @@ -2065,12 +2103,12 @@ bool PrimToPrimSpecImpl(const Model &p, PrimSpec &ps, std::string *err) { ps.metas() = p.meta; // TODO: variantSet - //ps.variantSets + // ps.variantSets return true; } -template<> +template <> bool PrimToPrimSpecImpl(const Xform &p, PrimSpec &ps, std::string *err) { (void)err; @@ -2083,48 +2121,48 @@ bool PrimToPrimSpecImpl(const Xform &p, PrimSpec &ps, std::string *err) { std::vector toks; Attribute xformOpOrderAttr; xformOpOrderAttr.set_value(toks); - ps.props().emplace("xformOpOrder", Property(xformOpOrderAttr, /* custom */false)); + ps.props().emplace("xformOpOrder", + Property(xformOpOrderAttr, /* custom */ false)); ps.metas() = p.meta; // TODO: variantSet - //ps.variantSets + // ps.variantSets return true; } -bool PrimToPrimSpec(const Prim &prim, PrimSpec &ps, std::string *err) -{ - -#define TO_PRIMSPEC(__ty) \ - if (prim.as<__ty>()) { \ +bool PrimToPrimSpec(const Prim &prim, PrimSpec &ps, std::string *err) { +#define TO_PRIMSPEC(__ty) \ + if (prim.as<__ty>()) { \ return PrimToPrimSpecImpl(*(prim.as<__ty>()), ps, err); \ } else - - TO_PRIMSPEC(Model) - { + TO_PRIMSPEC(Model) { if (err) { - (*err) += "Unsupported/unimplemented Prim type: " + prim.prim_type_name() + "\n"; + (*err) += + "Unsupported/unimplemented Prim type: " + prim.prim_type_name() + + "\n"; } return false; } #undef TO_PRIMSPEC - } -bool ShaderToPrimSpec(const UsdTransform2d &node, PrimSpec &ps, std::string *warn, std::string *err) -{ +bool ShaderToPrimSpec(const UsdTransform2d &node, PrimSpec &ps, + std::string *warn, std::string *err) { (void)warn; -#define TO_PROPERTY(__prop_name, __v) { \ - Property prop; \ - if (!ToProperty(__v, prop, err)) { \ - PUSH_ERROR_AND_RETURN(fmt::format("Convert {} to Property failed.\n", __prop_name)); \ - } \ - ps.props()[__prop_name] = prop; \ -} +#define TO_PROPERTY(__prop_name, __v) \ + { \ + Property prop; \ + if (!ToProperty(__v, prop, err)) { \ + PUSH_ERROR_AND_RETURN( \ + fmt::format("Convert {} to Property failed.\n", __prop_name)); \ + } \ + ps.props()[__prop_name] = prop; \ + } // inputs TO_PROPERTY("inputs:in", node.in) @@ -2141,7 +2179,8 @@ bool ShaderToPrimSpec(const UsdTransform2d &node, PrimSpec &ps, std::string *war ps.props()[prop.first] = prop.second; } - ps.props()[kInfoId] = Property(Attribute::Uniform(value::token(kUsdTransform2d))); + ps.props()[kInfoId] = + Property(Attribute::Uniform(value::token(kUsdTransform2d))); ps.metas() = node.metas(); ps.name() = node.name; ps.specifier() = node.spec; @@ -2149,7 +2188,9 @@ bool ShaderToPrimSpec(const UsdTransform2d &node, PrimSpec &ps, std::string *war return true; } -std::vector GetGeomSubsets(const tinyusdz::Stage &stage, const tinyusdz::Path &prim_path, const tinyusdz::value::token &familyName, bool prim_must_be_geommesh) { +std::vector GetGeomSubsets( + const tinyusdz::Stage &stage, const tinyusdz::Path &prim_path, + const tinyusdz::value::token &familyName, bool prim_must_be_geommesh) { std::vector result; const Prim *pprim{nullptr}; @@ -2169,7 +2210,6 @@ std::vector GetGeomSubsets(const tinyusdz::Stage &stage, con for (const auto &p : pprim->children()) { if (auto pv = p.as()) { if (familyName.valid()) { - if (pv->familyName.authored()) { if (pv->familyName.get_value().has_value()) { const value::token &tok = pv->familyName.get_value().value(); @@ -2192,7 +2232,9 @@ std::vector GetGeomSubsets(const tinyusdz::Stage &stage, con return result; } -std::vector GetGeomSubsetChildren(const tinyusdz::Prim &prim, const tinyusdz::value::token &familyName, bool prim_must_be_geommesh) { +std::vector GetGeomSubsetChildren( + const tinyusdz::Prim &prim, const tinyusdz::value::token &familyName, + bool prim_must_be_geommesh) { std::vector result; if (prim_must_be_geommesh && !prim.is()) { @@ -2203,7 +2245,6 @@ std::vector GetGeomSubsetChildren(const tinyusdz::Prim &prim for (const auto &p : prim.children()) { if (auto pv = p.as()) { if (familyName.valid()) { - if (pv->familyName.authored()) { if (pv->familyName.get_value().has_value()) { const value::token &tok = pv->familyName.get_value().value(); @@ -2231,13 +2272,15 @@ bool ShaderToPrimSpec(const UsdUVTexture &node, PrimSpec &ps, std::string *warn, { (void)warn; -#define TO_PROPERTY(__prop_name, __v) { \ - Property prop; \ - if (!ToProperty(__v, prop, err)) { \ - PUSH_ERROR_AND_RETURN(fmt::format("Convert {} to Property failed.\n", __prop_name)); \ - } \ - ps.props()[__prop_name] = prop; \ -} +#define TO_PROPERTY(__prop_name, __v) \ + { \ + Property prop; \ + if (!ToProperty(__v, prop, err)) { \ + PUSH_ERROR_AND_RETURN( \ + fmt::format("Convert {} to Property failed.\n", __prop_name)); \ + } \ + ps.props()[__prop_name] = prop; \ + } // inputs TO_PROPERTY("inputs:in", node.in) @@ -2277,8 +2320,9 @@ bool GetCollection(const Prim &prim, const Collection **dst) { return ret; } -bool IsPathIncluded(const CollectionMembershipQuery &query, const Stage &stage, const Path &abs_path, const CollectionInstance::ExpansionRule expansionRule) { - +bool IsPathIncluded(const CollectionMembershipQuery &query, const Stage &stage, + const Path &abs_path, + const CollectionInstance::ExpansionRule expansionRule) { (void)query; (void)stage; (void)expansionRule; @@ -2294,14 +2338,11 @@ bool IsPathIncluded(const CollectionMembershipQuery &query, const Stage &stage, } return false; - } std::vector> -GetBlendShapes( - const tinyusdz::Stage &stage, - const tinyusdz::Prim &prim, std::string *err) { - +GetBlendShapes(const tinyusdz::Stage &stage, const tinyusdz::Prim &prim, + std::string *err) { std::vector> dst; auto *pmesh = prim.as(); @@ -2324,26 +2365,33 @@ GetBlendShapes( if (err) { (*err) += "Failed to get `skel:blendShapes` attribute.\n"; } - return std::vector>{}; + return std::vector< + std::pair>{}; } if (pmesh->blendShapeTargets.value().is_path()) { if (blendShapeNames.size() != 1) { if (err) { - (*err) += "Array size mismatch with `skel:blendShapes` and " - "`skel:blendShapeTargets`.\n"; + (*err) += + "Array size mismatch with `skel:blendShapes` and " + "`skel:blendShapeTargets`.\n"; } - return std::vector>{}; + return std::vector< + std::pair>{}; } const Path &targetPath = pmesh->blendShapeTargets.value().targetPath; const Prim *bsprim{nullptr}; if (!stage.find_prim_at_path(targetPath, bsprim, err)) { - return std::vector>{}; + return std::vector< + std::pair>{}; } if (!bsprim) { - if (err) { (*err) += "Internal error. BlendShape Prim is nullptr.\n"; } - return std::vector>{}; + if (err) { + (*err) += "Internal error. BlendShape Prim is nullptr.\n"; + } + return std::vector< + std::pair>{}; } if (const auto *bs = bsprim->as()) { @@ -2351,24 +2399,31 @@ GetBlendShapes( } else { if (err) { (*err) += fmt::format("{} is not BlendShape Prim.\n", - targetPath.full_path_name()); + targetPath.full_path_name()); } - return std::vector>{}; + return std::vector< + std::pair>{}; } } else if (pmesh->blendShapeTargets.value().is_pathvector()) { if (blendShapeNames.size() != pmesh->blendShapeTargets.value().targetPathVector.size()) { - if (err) { (*err) += - "Array size mismatch with `skel:blendShapes` and " - "`skel:blendShapeTargets`.\n"; } - return std::vector>{}; + if (err) { + (*err) += + "Array size mismatch with `skel:blendShapes` and " + "`skel:blendShapeTargets`.\n"; + } + return std::vector< + std::pair>{}; } } else { - if (err) { (*err) += - "Invalid or unsupported definition of `skel:blendShapeTargets` " - "relationship.\n"; } - return std::vector>{}; + if (err) { + (*err) += + "Invalid or unsupported definition of `skel:blendShapeTargets` " + "relationship.\n"; + } + return std::vector< + std::pair>{}; } for (size_t i = 0; @@ -2377,19 +2432,26 @@ GetBlendShapes( pmesh->blendShapeTargets.value().targetPathVector[i]; const Prim *bsprim{nullptr}; if (!stage.find_prim_at_path(targetPath, bsprim, err)) { - return std::vector>{}; + return std::vector< + std::pair>{}; } if (!bsprim) { - if (err) { (*err) += "Internal error. BlendShape Prim is nullptr."; } - return std::vector>{}; + if (err) { + (*err) += "Internal error. BlendShape Prim is nullptr."; + } + return std::vector< + std::pair>{}; } if (const auto *bs = bsprim->as()) { dst.push_back(std::make_pair(blendShapeNames[0].str(), bs)); } else { - if (err) { (*err) += fmt::format("{} is not BlendShape Prim.", - targetPath.full_path_name()); } - return std::vector>{}; + if (err) { + (*err) += fmt::format("{} is not BlendShape Prim.", + targetPath.full_path_name()); + } + return std::vector< + std::pair>{}; } } } @@ -2397,7 +2459,9 @@ GetBlendShapes( return dst; } -bool GetGeomPrimvar(const Stage &stage, const GPrim *gprim, const std::string &varname, GeomPrimvar *out_primvar, std::string *err) { +bool GetGeomPrimvar(const Stage &stage, const GPrim *gprim, + const std::string &varname, GeomPrimvar *out_primvar, + std::string *err) { if (!out_primvar) { PUSH_ERROR_AND_RETURN("Output GeomPrimvar is nullptr."); } @@ -2410,7 +2474,7 @@ bool GetGeomPrimvar(const Stage &stage, const GPrim *gprim, const std::string &v constexpr auto kPrimvars = "primvars:"; constexpr auto kIndices = ":indices"; - + std::string primvar_name = kPrimvars + varname; const auto it = gprim->props.find(primvar_name); @@ -2422,9 +2486,10 @@ bool GetGeomPrimvar(const Stage &stage, const GPrim *gprim, const std::string &v const Attribute &attr = it->second.get_attribute(); if (attr.is_connection()) { - // follow targetPath to get Attribute + // follow targetPath to get Attribute Attribute terminal_attr; - bool ret = tydra::GetTerminalAttribute(stage, attr, primvar_name, &terminal_attr, err); + bool ret = tydra::GetTerminalAttribute(stage, attr, primvar_name, + &terminal_attr, err); if (!ret) { return false; } @@ -2440,13 +2505,14 @@ bool GetGeomPrimvar(const Stage &stage, const GPrim *gprim, const std::string &v if (attr.metas().interpolation.has_value()) { primvar.set_interpolation(attr.metas().interpolation.value()); } - if (attr.metas().elementSize.has_value()) { + if (attr.metas().elementSize.has_value()) { primvar.set_elementSize(attr.metas().elementSize.value()); } // TODO: copy other attribute metas? } else { - PUSH_ERROR_AND_RETURN(fmt::format("{} is not Attribute(Maybe Relationship?).", primvar_name)); + PUSH_ERROR_AND_RETURN( + fmt::format("{} is not Attribute(Maybe Relationship?).", primvar_name)); } // has indices? @@ -2459,13 +2525,16 @@ bool GetGeomPrimvar(const Stage &stage, const GPrim *gprim, const std::string &v if (!(primvar.get_attribute().type_id() & value::TYPE_ID_1D_ARRAY_BIT)) { PUSH_ERROR_AND_RETURN( - fmt::format("Indexed GeomPrimVar with scalar PrimVar Attribute is not supported. PrimVar name: {}", primvar_name)); + fmt::format("Indexed GeomPrimVar with scalar PrimVar Attribute is " + "not supported. PrimVar name: {}", + primvar_name)); } if (indexAttr.is_connection()) { - // follow targetPath to get Attribute + // follow targetPath to get Attribute Attribute terminal_indexAttr; - bool ret = tydra::GetTerminalAttribute(stage, indexAttr, index_name, &terminal_indexAttr, err); + bool ret = tydra::GetTerminalAttribute(stage, indexAttr, index_name, + &terminal_indexAttr, err); if (!ret) { return false; } @@ -2474,12 +2543,13 @@ bool GetGeomPrimvar(const Stage &stage, const GPrim *gprim, const std::string &v const auto &ts = terminal_indexAttr.get_var().ts_raw(); TypedTimeSamples> tss; if (!tss.from_timesamples(ts)) { - PUSH_ERROR_AND_RETURN(fmt::format("Index Attribute seems not an timesamples with int[] type: {}", index_name)); + PUSH_ERROR_AND_RETURN(fmt::format( + "Index Attribute seems not an timesamples with int[] type: {}", + index_name)); } - + primvar.set_indices(tss); } else if (terminal_indexAttr.is_value()) { - // TODO: Support uint[]? std::vector indices; if (!terminal_indexAttr.get_value(&indices)) { @@ -2489,16 +2559,17 @@ bool GetGeomPrimvar(const Stage &stage, const GPrim *gprim, const std::string &v } primvar.set_indices(indices); - } - + } else if (indexAttr.is_timesamples()) { const auto &ts = indexAttr.get_var().ts_raw(); TypedTimeSamples> tss; if (!tss.from_timesamples(ts)) { - PUSH_ERROR_AND_RETURN(fmt::format("Index Attribute seems not an timesamples with int[] type: {}", index_name)); + PUSH_ERROR_AND_RETURN(fmt::format( + "Index Attribute seems not an timesamples with int[] type: {}", + index_name)); } - + primvar.set_indices(tss); } else if (indexAttr.is_blocked()) { // Value blocked. e.g. `float2[] primvars:st:indices = None` @@ -2513,7 +2584,6 @@ bool GetGeomPrimvar(const Stage &stage, const GPrim *gprim, const std::string &v indexAttr.type_name())); } - primvar.set_indices(indices); } else { PUSH_ERROR_AND_RETURN("[Internal Error] Invalid Index Attribute."); @@ -2526,8 +2596,6 @@ bool GetGeomPrimvar(const Stage &stage, const GPrim *gprim, const std::string &v (*out_primvar) = primvar; return true; - - } namespace { @@ -2535,11 +2603,11 @@ namespace { // // visited_paths : To prevent circular referencing of attribute connection. // -bool GetTerminalAttributeImpl( - const tinyusdz::Stage &stage, const tinyusdz::Prim &prim, - const std::string &attr_name, Attribute *value, - std::string *err, std::set &visited_paths) { - +bool GetTerminalAttributeImpl(const tinyusdz::Stage &stage, + const tinyusdz::Prim &prim, + const std::string &attr_name, Attribute *value, + std::string *err, + std::set &visited_paths) { DCOUT("Prim : " << prim.element_path().element_name() << "(" << prim.type_name() << ") attr_name " << attr_name); @@ -2552,7 +2620,8 @@ bool GetTerminalAttributeImpl( // Follow connection target Path(singple targetPath only). std::vector pv = prop.get_attribute().connections(); if (pv.empty()) { - PUSH_ERROR_AND_RETURN(fmt::format("Connection targetPath is empty for Attribute {}.", attr_name)); + PUSH_ERROR_AND_RETURN(fmt::format( + "Connection targetPath is empty for Attribute {}.", attr_name)); } if (pv.size() > 1) { @@ -2584,7 +2653,7 @@ bool GetTerminalAttributeImpl( visited_paths.insert(abs_path); return GetTerminalAttributeImpl(stage, *targetPrim, targetPrimPropName, - value, err, visited_paths); + value, err, visited_paths); } else { PUSH_ERROR_AND_RETURN(targetPrimRet.error()); @@ -2597,9 +2666,8 @@ bool GetTerminalAttributeImpl( "Attribute `{}` is a define-only attribute(no value assigned).", attr_name)); } else if (prop.is_attribute()) { - (*value) = prop.get_attribute(); - + } else { // ??? PUSH_ERROR_AND_RETURN( @@ -2609,13 +2677,12 @@ bool GetTerminalAttributeImpl( return true; } -} // namespace - -bool GetTerminalAttribute( - const tinyusdz::Stage &stage, const tinyusdz::Attribute &attr, - const std::string &attr_name, Attribute *value, - std::string *err) { +} // namespace +bool GetTerminalAttribute(const tinyusdz::Stage &stage, + const tinyusdz::Attribute &attr, + const std::string &attr_name, Attribute *value, + std::string *err) { if (!value) { PUSH_ERROR_AND_RETURN("`value` arg is nullptr."); } @@ -2623,10 +2690,10 @@ bool GetTerminalAttribute( std::set visited_paths; if (attr.is_connection()) { - std::vector pv = attr.connections(); if (pv.empty()) { - PUSH_ERROR_AND_RETURN(fmt::format("Connection targetPath is empty for Attribute {}.", attr_name)); + PUSH_ERROR_AND_RETURN(fmt::format( + "Connection targetPath is empty for Attribute {}.", attr_name)); } if (pv.size() > 1) { @@ -2658,8 +2725,8 @@ bool GetTerminalAttribute( visited_paths.insert(abs_path); return GetTerminalAttributeImpl(stage, *targetPrim, targetPrimPropName, - value, err, visited_paths); - + value, err, visited_paths); + } else { PUSH_ERROR_AND_RETURN(targetPrimRet.error()); } @@ -2670,9 +2737,181 @@ bool GetTerminalAttribute( } return false; - } +namespace detail { + +static bool BuildSkelHierarchyImpl( + /* inout */ std::set &visitSet, + /* inout */ SkelNode &parentNode, + const std::vector &parentJointIds, + const std::vector &joints, + const std::vector &jointNames, + const std::vector bindTransforms, + const std::vector &restTransforms, + std::string *err = nullptr) { + // Simple linear search + for (size_t i = 0; i < parentJointIds.size(); i++) { + if (visitSet.count(i)) { + continue; + } + + int parentJointIdOfCurrIdx = parentJointIds[i]; + if (parentNode.joint_id == parentJointIdOfCurrIdx) { + DCOUT("add joint " << i << "(parent = " << parentJointIdOfCurrIdx << ")"); + SkelNode node; + node.joint_id = int(i); + node.joint_path = joints[i].str(); + node.joint_name = jointNames[i].str(); + node.bind_transform = bindTransforms[i]; + node.rest_transform = restTransforms[i]; + + visitSet.insert(i); + + // Recursively traverse children + if (!BuildSkelHierarchyImpl(visitSet, node, + parentJointIds, joints, jointNames, bindTransforms, + restTransforms, err)) { + return false; + } + + parentNode.children.emplace_back(std::move(node)); + } + } + + return true; +} + +} // namespace detail + +bool BuildSkelHierarchy(const Skeleton &skel, SkelNode &dst, std::string *err) { + if (!skel.joints.authored()) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Skeleton.joints attrbitue is not authored: {}", skel.name)); + } + + std::vector joints; + if (!skel.joints.get_value(&joints)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to get Skeleton.joints attrbitue: {}", skel.name)); + } + + if (joints.empty()) { + PUSH_ERROR_AND_RETURN( + fmt::format("Skeleton.joints attrbitue is empty: {}", skel.name)); + } + + std::vector jointNames; + + if (skel.jointNames.authored()) { + if (!skel.jointNames.get_value(&jointNames)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Failed to get Skeleton.jointNames attrbitue: {}", skel.name)); + } + + if (joints.size() != jointNames.size()) { + PUSH_ERROR_AND_RETURN( + fmt::format("Skeleton.joints.size {} must be equal to " + "Skeleton.jointNames.size {}: {}", + joints.size(), jointNames.size(), skel.name)); + } + } else { + // Use joints + jointNames.resize(joints.size()); + for (size_t i = 0; i < joints.size(); i++) { + jointNames[i] = joints[i]; + } + } + + + std::vector restTransforms; + if (skel.restTransforms.authored()) { + if (!skel.restTransforms.get_value(&restTransforms)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Failed to get Skeleton.restTransforms attrbitue: {}", skel.name)); + } + } else { + // TODO: Report error when `restTransforms` attribute is omitted? + restTransforms.assign(joints.size(), value::matrix4d::identity()); + } + + if (joints.size() != restTransforms.size()) { + PUSH_ERROR_AND_RETURN( + fmt::format("Skeleton.joints.size {} must be equal to " + "Skeleton.restTransforms.size {}: {}", + joints.size(), restTransforms.size(), skel.name)); + } + + std::vector bindTransforms; + if (skel.bindTransforms.authored()) { + if (!skel.bindTransforms.get_value(&bindTransforms)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Failed to get Skeleton.bindTransforms attrbitue: {}", skel.name)); + } + } else { + // TODO: Report error when `restTransforms` attribute is omitted? + restTransforms.assign(joints.size(), value::matrix4d::identity()); + } + + if (joints.size() != bindTransforms.size()) { + PUSH_ERROR_AND_RETURN( + fmt::format("Skeleton.joints.size {} must be equal to " + "Skeleton.bindTransforms.size {}: {}", + joints.size(), bindTransforms.size(), skel.name)); + } + + // Get flattened representation of joint hierarchy with BuildSkelTopology. + // For root node, parentJointId = -1. + std::vector parentJointIds; + if (!BuildSkelTopology(joints, parentJointIds, err)) { + return false; + } + + // Just in case. Chek if topology is single-rooted. + auto nroots = std::count_if(parentJointIds.begin(), parentJointIds.end(), + [](int x) { return x == -1; }); + + if (nroots == 0) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Invalid Skel topology. No root joint found: {}", skel.name)); + } + + if (nroots != 1) { + PUSH_ERROR_AND_RETURN( + fmt::format("Invalid Skel topology. Topology must be single-rooted, " + "but it has {} roots: {}", + nroots, skel.name)); + } + + std::set visitSet; + + SkelNode root; + + auto it = std::find(parentJointIds.begin(), parentJointIds.end(), -1); + if (it == parentJointIds.end()) { + PUSH_ERROR_AND_RETURN("Internal error."); + } + size_t rootIdx = size_t(std::distance(parentJointIds.begin(), it)); + + root.joint_name = jointNames[rootIdx].str(); + root.joint_path = joints[rootIdx].str(); + root.joint_id = int(rootIdx); + root.bind_transform = bindTransforms[rootIdx]; + root.rest_transform = restTransforms[rootIdx]; + + DCOUT("parentJointIds = " << parentJointIds); + + // Construct hierachy from flattened id array. + if (!detail::BuildSkelHierarchyImpl(visitSet, root, parentJointIds, joints, jointNames, + bindTransforms, restTransforms, + err)) { + return false; + } + + dst = root; + + return true; +} } // namespace tydra } // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.hh b/contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.hh index cc6a5cf9c..12d186e97 100644 --- a/contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.hh +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.hh @@ -517,46 +517,40 @@ bool IsPathIncluded(const CollectionMembershipQuery &query, const Stage &stage, // struct SkelNode { - std::string jointElementName; // elementName(leaf node name) of jointPath. - std::string jointPath; // joints in UsdSkel. Relative or Absolute Prim + //std::string jointElementName; // elementName(leaf node name) of jointPath. + std::string joint_path; // joints in UsdSkel. Relative or Absolute Prim // path(e.g. "root/head", "/root/head") - std::string jointName; // jointNames in UsdSkel - int jointId{-1}; // jointIndex(array index in UsdSkel joints) - value::matrix4d bindTransform{value::matrix4d::identity()}; - value::matrix4d restTransform{value::matrix4d::identity()}; - int parentNodeIndex{-1}; + std::string joint_name; // jointNames in UsdSkel + int joint_id{-1}; // jointIndex(array index in UsdSkel joints) - std::vector childNodeIndices; + value::matrix4d bind_transform{value::matrix4d::identity()}; + value::matrix4d rest_transform{value::matrix4d::identity()}; + //int parentNodeIndex{-1}; + + std::vector children; }; class SkelHierarchy { public: SkelHierarchy() = default; - const std::string &name() const { return _name; } + std::string prim_name; // Skeleleton Prim name + std::string abs_path; // Absolute path to Skeleleton Prim + std::string display_name; // `displayName` Prim meta - bool get_root(SkelNode &dst) { - if (_skel_nodes.empty()) { - _err += "SkelNode is Empty\n"; - return false; - } + SkelNode root_node; - dst = _skel_nodes[0]; - return true; - } + int anim_id{-1}; // Default animation(SkelAnimation) attached to Skeleton private: - std::string _warm; - std::string _err; - std::string _name; // Skeleleton Prim name - std::vector _skel_nodes; // [0] = root node. + }; /// /// Extract skeleleton info from Skeleton and build skeleton(bone) hierarchy. /// -bool BuildSkelHierarchy(const Stage &stage, const Skeleton &skel, - SkelHierarchy &dst, std::string *err = nullptr); +bool BuildSkelHierarchy(const Skeleton &skel, + SkelNode &dst, std::string *err = nullptr); // // For USDZ AR extensions diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdGeom.cc b/contrib/tinyusdz/tinyusdz_repo/src/usdGeom.cc index 7c2e799d2..ca6f619e9 100644 --- a/contrib/tinyusdz/tinyusdz_repo/src/usdGeom.cc +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdGeom.cc @@ -827,29 +827,29 @@ bool GeomSubset::ValidateSubsets( valid = false; } } + } - // Make sure every index appears exactly once if it's a partition. - if ((familyType == FamilyType::Partition) && (indicesInFamily.size() != elementCount)) { - ss << fmt::format("ValidateSubsets: The number of unique indices {} must be equal to input elementCount {}\n", indicesInFamily.size(), elementCount); - valid = false; - } - // Ensure that the indices are in the range [0, faceCount) - size_t maxIndex = static_cast(*indicesInFamily.rbegin()); - int minIndex = *indicesInFamily.begin(); + // Make sure every index appears exactly once if it's a partition. + if ((familyType == FamilyType::Partition) && (indicesInFamily.size() != elementCount)) { + ss << fmt::format("ValidateSubsets: The number of unique indices {} must be equal to input elementCount {}\n", indicesInFamily.size(), elementCount); + valid = false; + } - if (maxIndex >= elementCount) { - ss << fmt::format("ValidateSubsets: All indices must be in range [0, elementSize {}), but one or more indices are greater than elementSize. Maximum = {}\n", elementCount, maxIndex); + // Ensure that the indices are in the range [0, faceCount) + size_t maxIndex = static_cast(*indicesInFamily.rbegin()); + int minIndex = *indicesInFamily.begin(); - valid = false; - } + if (maxIndex >= elementCount) { + ss << fmt::format("ValidateSubsets: All indices must be in range [0, elementSize {}), but one or more indices are greater than elementSize. Maximum = {}\n", elementCount, maxIndex); - if (minIndex < 0) { - ss << fmt::format("ValidateSubsets: Found one or more indices that are less than 0. Minumum = {}\n", minIndex); + valid = false; + } - valid = false; - } + if (minIndex < 0) { + ss << fmt::format("ValidateSubsets: Found one or more indices that are less than 0. Minumum = {}\n", minIndex); + valid = false; } if (!valid) { diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdSkel.cc b/contrib/tinyusdz/tinyusdz_repo/src/usdSkel.cc index 122c7297e..db71542b7 100644 --- a/contrib/tinyusdz/tinyusdz_repo/src/usdSkel.cc +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdSkel.cc @@ -137,15 +137,12 @@ bool BuildSkelTopology( } // path name <-> index map - std::map pathMap; + std::map pathMap; for (size_t i = 0; i < paths.size(); i++) { - pathMap[paths[i]] = int(i); + pathMap[paths[i].prim_part()] = int(i); } - std::vector parentIndices; - parentIndices.assign(paths.size(), -1); - - auto GetParentIndex = [](const std::map &_pathMap, const Path &path) -> int { + auto GetParentIndex = [](const std::map &_pathMap, const Path &path) -> int { if (path.is_root_path()) { return -1; } @@ -163,8 +160,9 @@ bool BuildSkelTopology( uint32_t depth = 0; while (parentPath.is_valid() && !parentPath.is_root_path()) { - if (_pathMap.count(parentPath)) { - return _pathMap.at(parentPath); + if (_pathMap.count(parentPath.prim_part())) { + return _pathMap.at(parentPath.prim_part()); + } else { } parentPath = parentPath.get_parent_prim_path(); diff --git a/include/assimp/BaseImporter.h b/include/assimp/BaseImporter.h index 447784e75..167b254d6 100644 --- a/include/assimp/BaseImporter.h +++ b/include/assimp/BaseImporter.h @@ -277,7 +277,8 @@ public: // static utilities const std::string &pFile, const char *ext0, const char *ext1 = nullptr, - const char *ext2 = nullptr); + const char *ext2 = nullptr, + const char *ext3 = nullptr); // ------------------------------------------------------------------- /** @brief Check whether a file has one of the passed file extensions