Update with blendshape support

pull/5551/head
Steve M 2024-05-02 16:14:20 -07:00
parent b343d0f730
commit e51eaf54c8
17 changed files with 1832 additions and 599 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
)

View File

@ -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);
}

View File

@ -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);

View File

@ -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 {

View File

@ -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";

View File

@ -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();
}

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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();

View File

@ -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