Update with blendshape support
parent
b343d0f730
commit
e51eaf54c8
|
@ -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 {
|
||||
|
|
|
@ -50,6 +50,7 @@ Copyright (c) 2006-2024, assimp team
|
|||
// internal headers
|
||||
#include <assimp/ai_assert.h>
|
||||
#include <assimp/anim.h>
|
||||
#include <assimp/CreateAnimMesh.h>
|
||||
#include <assimp/DefaultIOSystem.h>
|
||||
#include <assimp/DefaultLogger.hpp>
|
||||
#include <assimp/fast_atof.h>
|
||||
|
@ -57,20 +58,20 @@ Copyright (c) 2006-2024, assimp team
|
|||
#include <assimp/importerdesc.h>
|
||||
#include <assimp/IOStreamBuffer.h>
|
||||
#include <assimp/IOSystem.hpp>
|
||||
#include <assimp/scene.h>
|
||||
#include <assimp/StringUtils.h>
|
||||
#include <assimp/StreamReader.h>
|
||||
|
||||
#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<size_t, tinyusdz::tydra::Node> 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<unsigned int>(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<int>(render_scene.meshes[meshIdx].vertexArrayType) <<
|
||||
// ", Indexed: " << static_cast<int>(tinyusdz::tydra::RenderMesh::VertexArrayType::Indexed) <<
|
||||
// ", Facevarying: " << static_cast<int>(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<const float *>(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<const float *>(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<float, 3> &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<size_t, tinyusdz::tydra::Node> &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<size_t, tinyusdz::tydra::Node> &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<size_t, tinyusdz::tydra::Node> &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
|
||||
|
|
|
@ -46,6 +46,7 @@ Copyright (c) 2006-2024, assimp team
|
|||
#define AI_USDLOADER_IMPL_TINYUSDZ_H_INCLUDED
|
||||
|
||||
#include <assimp/BaseImporter.h>
|
||||
#include <assimp/scene.h>
|
||||
#include <assimp/types.h>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
@ -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<size_t, tinyusdz::tydra::Node> &meshNodes,
|
||||
const std::string &nameWExt
|
||||
);
|
||||
|
||||
aiNode *nodes(
|
||||
const tinyusdz::tydra::RenderScene &render_scene,
|
||||
std::map<size_t, tinyusdz::tydra::Node> &meshNodes,
|
||||
const std::string &nameWExt);
|
||||
|
||||
aiNode *nodesRecursive(
|
||||
aiNode *pNodeParent,
|
||||
const tinyusdz::tydra::Node &node,
|
||||
std::map<size_t, tinyusdz::tydra::Node> &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
|
||||
|
|
|
@ -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<float, 3> &scaleOrPosIn) {
|
||||
return aiVector3D(scaleOrPosIn[0], scaleOrPosIn[1], scaleOrPosIn[2]);
|
||||
}
|
||||
|
||||
aiQuaternion Assimp::tinyUsdzQuatToAiQuat(const std::array<float, 4> &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
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
#ifndef AI_USDLOADER_IMPL_TINYUSDZ_HELPER_H_INCLUDED
|
||||
#define AI_USDLOADER_IMPL_TINYUSDZ_HELPER_H_INCLUDED
|
||||
|
||||
#include <assimp/BaseImporter.h>
|
||||
#include <assimp/scene.h>
|
||||
#include <assimp/types.h>
|
||||
#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<float, 3> &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<float, 4> &quatIn);
|
||||
|
||||
} // namespace Assimp
|
||||
#endif // AI_USDLOADER_IMPL_TINYUSDZ_HELPER_H_INCLUDED
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -250,9 +250,10 @@ void BaseImporter::GetExtensionList(std::set<std::string> &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<std::string> extensions;
|
||||
for (const char* ext : {ext0, ext1, ext2}) {
|
||||
for (const char* ext : {ext0, ext1, ext2, ext3}) {
|
||||
if (ext == nullptr) continue;
|
||||
extensions.emplace(ext);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<T> get_value() const {
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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<vec3> &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<value::float3> 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<uint32_t, std::vector<uint32_t>> 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<value::float3> tmpPointOffsets;
|
||||
std::vector<value::float3> tmpNormalOffsets;
|
||||
std::vector<uint32_t> 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<uint32_t> &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<Animation> 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<value::token> 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<std::vector<value::float3>> 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<std::vector<value::quatf>> 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<std::vector<value::half3>> 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<std::vector<value::float3>> &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<std::vector<value::float3>> 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<value::token> 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<std::string, std::map<AnimationChannel::ChannelType, AnimationChannel>> channelMap;
|
||||
|
||||
// Joint animations
|
||||
if (joints.size()) {
|
||||
StringAndIdMap jointIdMap;
|
||||
|
||||
for (const auto &joint : joints) {
|
||||
uint64_t id = jointIdMap.size();
|
||||
jointIdMap.add(joint.str(), id);
|
||||
}
|
||||
|
||||
Animatable<std::vector<value::float3>> 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<std::vector<value::quatf>> 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<std::vector<value::half3>> 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<std::vector<value::float3>> &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<value::float3> 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<std::vector<value::quatf>> &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<value::float4> 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<std::vector<value::half3>> &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<value::float3> 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<value::float3> translation;
|
||||
std::vector<value::float4> rotation;
|
||||
std::vector<value::float3> 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<value::quatf> _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<value::half3> _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<value::float3> s;
|
||||
s.t = std::numeric_limits<float>::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<value::float4> s;
|
||||
s.t = std::numeric_limits<float>::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<value::float3> s;
|
||||
s.t = std::numeric_limits<float>::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<std::string, std::vector<AnimationSample<float>>> 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<float> s;
|
||||
s.t = std::numeric_limits<float>::quiet_NaN();
|
||||
s.value = 1.0f;
|
||||
weightsMap[bs.str()].push_back(s);
|
||||
}
|
||||
} else {
|
||||
|
||||
Animatable<std::vector<float>> 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<std::vector<float>> &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<float> 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<float> 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<float> s;
|
||||
s.t = std::numeric_limits<float>::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<std::vector<value::quatf>> &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<std::vector<value::half3>> &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<value::float3> translation;
|
||||
std::vector<value::float4> rotation;
|
||||
std::vector<value::float3> 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<value::quatf> _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<value::half3> _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<std::vector<value::float3>> sample;
|
||||
sample.t = std::numeric_limits<float>::quiet_NaN();
|
||||
sample.value = translation;
|
||||
channel_txs.translations.samples.push_back(sample);
|
||||
}
|
||||
|
||||
if (is_rotations_timesamples) {
|
||||
} else {
|
||||
AnimationSample<std::vector<value::float4>> sample;
|
||||
sample.t = std::numeric_limits<float>::quiet_NaN();
|
||||
sample.value = rotation;
|
||||
channel_rots.rotations.samples.push_back(sample);
|
||||
}
|
||||
|
||||
if (is_scales_timesamples) {
|
||||
} else {
|
||||
AnimationSample<std::vector<value::float3>> sample;
|
||||
sample.t = std::numeric_limits<float>::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<Animation> *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<Skeleton>()) {
|
||||
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<SkelAnimation>()) {
|
||||
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<typename T>
|
||||
std::string PrintAnimationSamples(const std::vector<AnimationSample<T>> &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<AnimationChannel::ChannelType, AnimationChannel> &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();
|
||||
}
|
||||
|
|
|
@ -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<std::vector<mat4>> transforms;
|
||||
AnimationSampler<mat4> transforms;
|
||||
|
||||
AnimationSampler<std::vector<vec3>> translations;
|
||||
AnimationSampler<std::vector<quat>> rotations; // Rotation is represented as quaternions
|
||||
AnimationSampler<std::vector<vec3>> scales; // half-types are upcasted to float precision
|
||||
AnimationSampler<std::vector<float>> weights;
|
||||
AnimationSampler<vec3> translations;
|
||||
AnimationSampler<quat> rotations; // Rotation is represented as quaternions
|
||||
AnimationSampler<vec3> scales; // half-types are upcasted to float precision
|
||||
AnimationSampler<float> 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<AnimationChannel> channels;
|
||||
|
||||
// key = joint, value = (key: channel_type, value: channel_value)
|
||||
std::map<std::string, std::map<AnimationChannel::ChannelType, AnimationChannel>> channels_map;
|
||||
|
||||
// For blendshapes
|
||||
// key = blendshape name, value = timesamped weights
|
||||
// TODO: in-between weight
|
||||
std::map<std::string, std::vector<AnimationSample<float>>> 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<vec3> points; // varying is 'vertex'.
|
||||
//VertexArrayType vertexArrayType{VertexArrayType::Facevarying};
|
||||
|
||||
std::vector<vec3> 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<TextureImage> images;
|
||||
std::vector<BufferData> buffers;
|
||||
std::vector<SkelHierarchy> skeletons;
|
||||
std::vector<Animation> 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<Animation> *out_anim);
|
||||
|
||||
bool BuildNodeHierarchyImpl(
|
||||
const RenderSceneConverterEnv &env,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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<int> childNodeIndices;
|
||||
value::matrix4d bind_transform{value::matrix4d::identity()};
|
||||
value::matrix4d rest_transform{value::matrix4d::identity()};
|
||||
//int parentNodeIndex{-1};
|
||||
|
||||
std::vector<SkelNode> 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<SkelNode> _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
|
||||
|
|
|
@ -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<size_t>(*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<size_t>(*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) {
|
||||
|
|
|
@ -137,15 +137,12 @@ bool BuildSkelTopology(
|
|||
}
|
||||
|
||||
// path name <-> index map
|
||||
std::map<Path, int> pathMap;
|
||||
std::map<std::string, int> pathMap;
|
||||
for (size_t i = 0; i < paths.size(); i++) {
|
||||
pathMap[paths[i]] = int(i);
|
||||
pathMap[paths[i].prim_part()] = int(i);
|
||||
}
|
||||
|
||||
std::vector<int> parentIndices;
|
||||
parentIndices.assign(paths.size(), -1);
|
||||
|
||||
auto GetParentIndex = [](const std::map<Path, int> &_pathMap, const Path &path) -> int {
|
||||
auto GetParentIndex = [](const std::map<std::string, int> &_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();
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue