From cb295d548b9e1956f8857926f2b3913dae27a335 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 24 Apr 2024 09:15:17 +0900 Subject: [PATCH] [Tydra] Initial support of SkelAnimation convert. --- src/tydra/render-data.cc | 136 ++++++++++++++++++++++++++++++++------ src/tydra/render-data.hh | 7 +- src/tydra/scene-access.hh | 2 + src/tydra/usd-export.cc | 50 +++++++++++--- 4 files changed, 161 insertions(+), 34 deletions(-) diff --git a/src/tydra/render-data.cc b/src/tydra/render-data.cc index 77d67cce..5c115ef7 100644 --- a/src/tydra/render-data.cc +++ b/src/tydra/render-data.cc @@ -3580,7 +3580,8 @@ bool RenderSceneConverter::ConvertMesh( if (skelPath.is_valid()) { SkelHierarchy skel; - if (!ConvertSkeletonImpl(env, abs_path.full_path_name(), mesh, &skel)) { + nonstd::optional anim; + if (!ConvertSkeletonImpl(env, mesh, &skel, &anim)) { return false; } DCOUT("Converted skeleton attached to : " << abs_path); @@ -3589,6 +3590,11 @@ bool RenderSceneConverter::ConvertMesh( 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)); @@ -3598,6 +3604,7 @@ bool RenderSceneConverter::ConvertMesh( } dst.skel_id = skel_id; + } } @@ -5193,13 +5200,14 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e } // - // NOTE: both timeSamples and default value are authored, timeSamples wins. + // NOTE: When both timeSamples and default value are authored, timeSamples wins. // bool is_translations_timesamples = false; bool is_rotations_timesamples = false; bool is_scales_timesamples = false; if (translations.is_timesamples()) { + DCOUT("Convert ttranslations"); const TypedTimeSamples> &ts_txs = translations.get_timesamples(); if (ts_txs.get_samples().empty()) { @@ -5219,7 +5227,7 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e s.value = sample.value[j]; std::string jointName = jointIdMap.at(j); - auto &it = channelMap.at(jointName).at(AnimationChannel::ChannelType::Translation); + auto &it = channelMap[jointName][AnimationChannel::ChannelType::Translation]; if (it.translations.samples.empty()) { it.type = AnimationChannel::ChannelType::Translation; } @@ -5231,22 +5239,62 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e is_translations_timesamples = true; } - const TypedTimeSamples> &ts_rots = rotations.get_timesamples(); - for (const auto &sample : ts_rots.get_samples()) { - if (!sample.blocked) { - if (sample.value.size() != joints.size()) { - PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} rotations.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path)); + if (rotations.is_timesamples()) { + const TypedTimeSamples> &ts_rots = rotations.get_timesamples(); + DCOUT("Convert rotations"); + for (const auto &sample : ts_rots.get_samples()) { + if (!sample.blocked) { + if (sample.value.size() != joints.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} rotations.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path)); + } + for (size_t j = 0; j < sample.value.size(); j++) { + AnimationSample s; + s.t = float(sample.t); + s.value[0] = sample.value[j][0]; + s.value[1] = sample.value[j][1]; + s.value[2] = sample.value[j][2]; + s.value[3] = sample.value[j][3]; + + std::string jointName = jointIdMap.at(j); + auto &it = channelMap[jointName][AnimationChannel::ChannelType::Rotation]; + if (it.rotations.samples.empty()) { + it.type = AnimationChannel::ChannelType::Rotation; + } + it.rotations.samples.push_back(s); + } + } } + is_rotations_timesamples = true; } - const TypedTimeSamples> &ts_scales = scales.get_timesamples(); - for (const auto &sample : ts_scales.get_samples()) { - if (!sample.blocked) { - if (sample.value.size() != joints.size()) { - PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} scales.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path)); + if (scales.is_timesamples()) { + const TypedTimeSamples> &ts_scales = scales.get_timesamples(); + DCOUT("Convert scales"); + for (const auto &sample : ts_scales.get_samples()) { + if (!sample.blocked) { + if (sample.value.size() != joints.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} scales.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path)); + } + + for (size_t j = 0; j < sample.value.size(); j++) { + AnimationSample s; + s.t = float(sample.t); + s.value[0] = value::half_to_float(sample.value[j][0]); + s.value[1] = value::half_to_float(sample.value[j][1]); + s.value[2] = value::half_to_float(sample.value[j][2]); + + std::string jointName = jointIdMap.at(j); + auto &it = channelMap[jointName][AnimationChannel::ChannelType::Scale]; + if (it.scales.samples.empty()) { + it.type = AnimationChannel::ChannelType::Scale; + } + it.scales.samples.push_back(s); + } + } } + is_scales_timesamples = true; } // value at 'default' time. @@ -5256,6 +5304,7 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e // 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)); } @@ -5266,6 +5315,7 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e } if (rotations.is_scalar()) { + DCOUT("rot is not timeSampled"); std::vector _rotation; if (!rotations.get_scalar(&_rotation)) { PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `rotations` attribute in SkelAnimation: {}", abs_path)); @@ -5286,6 +5336,7 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e } if (scales.is_scalar()) { + DCOUT("scale is not timeSampled"); std::vector _scale; if (!scales.get_scalar(&_scale)) { PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `scales` attribute in SkelAnimation: {}", abs_path)); @@ -5305,6 +5356,7 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e } 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) { @@ -5320,12 +5372,14 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e } if (!is_rotations_timesamples) { + DCOUT("Reorder rotation samples"); for (const auto &joint : joints) { channelMap[joint.str()][AnimationChannel::ChannelType::Rotation].type = AnimationChannel::ChannelType::Rotation; AnimationSample s; s.t = std::numeric_limits::quiet_NaN(); uint64_t joint_id = jointIdMap.at(joint.str()); + DCOUT("rot joint_id " << joint_id); s.value = rotation[joint_id]; channelMap[joint.str()][AnimationChannel::ChannelType::Rotation].rotations.samples.clear(); channelMap[joint.str()][AnimationChannel::ChannelType::Rotation].rotations.samples.push_back(s); @@ -5333,6 +5387,7 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e } if (!is_scales_timesamples) { + DCOUT("Reorder scale samples"); for (const auto &joint : joints) { channelMap[joint.str()][AnimationChannel::ChannelType::Scale].type = AnimationChannel::ChannelType::Scale; @@ -5490,6 +5545,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. // @@ -5503,11 +5559,6 @@ bool RenderSceneConverter::ConvertToRenderScene( PUSH_ERROR_AND_RETURN(err); } - // - // 4. Convert Skeletons - // - // TODO - // // 5. Build node hierarchy from XformNode and meshes, materials, skeletons, // etc. @@ -5539,13 +5590,14 @@ 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 std::string &abs_path, const tinyusdz::GeomMesh &mesh, - SkelHierarchy *out_skel) { +bool RenderSceneConverter::ConvertSkeletonImpl(const RenderSceneConverterEnv &env, const tinyusdz::GeomMesh &mesh, + SkelHierarchy *out_skel, nonstd::optional *out_anim) { if (!out_skel) { return false; @@ -5582,10 +5634,52 @@ bool RenderSceneConverter::ConvertSkeletonImpl(const RenderSceneConverterEnv &en if (!BuildSkelHierarchy((*pskel), root, &_err)) { return false; } - dst.abs_path = abs_path; + dst.abs_path = skelPath.prim_part(); dst.prim_name = skelPrim->element_name(); dst.display_name = pskel->metas().displayName.value_or(""); dst.root_node = root; + + if (pskel->animationSource.has_value()) { + DCOUT("skel:animationSource"); + + const Relationship &animSourceRel = pskel->animationSource.value(); + + Path animSourcePath; + + if (animSourceRel.is_path()) { + animSourcePath = animSourceRel.targetPath; + } else if (animSourceRel.is_pathvector()) { + // Use the first one + if (animSourceRel.targetPathVector.size()) { + animSourcePath = animSourceRel.targetPathVector[0]; + } else { + PUSH_ERROR_AND_RETURN("`skel:animationSource` has invalid definition."); + } + } else { + PUSH_ERROR_AND_RETURN("`skel:animationSource` has invalid definition."); + } + + const Prim *animSourcePrim{nullptr}; + if (!env.stage.find_prim_at_path(animSourcePath, animSourcePrim, &_err)) { + return false; + } + + if (const auto panim = animSourcePrim->as()) { + DCOUT("Convert SkelAnimation"); + Animation anim; + if (!ConvertSkelAnimation(env, animSourcePath, *panim, &anim)) { + return false; + } + + DCOUT("Converted SkelAnimation"); + (*out_anim) = anim; + + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Target Prim of `skel:animationSource` must be `SkelAnimation` Prim, but got `{}`.", animSourcePrim->prim_type_name())); + } + + + } } else { PUSH_ERROR_AND_RETURN("Prim is not Skeleton."); } diff --git a/src/tydra/render-data.hh b/src/tydra/render-data.hh index f92e6f4d..ea81d62a 100644 --- a/src/tydra/render-data.hh +++ b/src/tydra/render-data.hh @@ -1602,6 +1602,7 @@ class RenderSceneConverter { StringAndIdMap textureMap; StringAndIdMap imageMap; StringAndIdMap bufferMap; + StringAndIdMap animationMap; int default_node{-1}; @@ -1614,6 +1615,7 @@ class RenderSceneConverter { std::vector images; std::vector buffers; std::vector skeletons; + std::vector animations; /// /// Convert GeomMesh to renderer-friendly mesh. @@ -1777,9 +1779,10 @@ class RenderSceneConverter { // // Get Skeleton assigned to the GeomMesh Prim and convert it to SkelHierarchy. + // Also get SkelAnimation attached to Skeleton(if exists) // - bool ConvertSkeletonImpl(const RenderSceneConverterEnv &env, const std::string &mesh_abs_path, const tinyusdz::GeomMesh &mesh, - SkelHierarchy *out_skel); + bool ConvertSkeletonImpl(const RenderSceneConverterEnv &env, const tinyusdz::GeomMesh &mesh, + SkelHierarchy *out_skel, nonstd::optional *out_anim); bool BuildNodeHierarchyImpl( const RenderSceneConverterEnv &env, diff --git a/src/tydra/scene-access.hh b/src/tydra/scene-access.hh index 865549ff..12d186e9 100644 --- a/src/tydra/scene-access.hh +++ b/src/tydra/scene-access.hh @@ -540,6 +540,8 @@ class SkelHierarchy { SkelNode root_node; + int anim_id{-1}; // Default animation(SkelAnimation) attached to Skeleton + private: }; diff --git a/src/tydra/usd-export.cc b/src/tydra/usd-export.cc index f3812368..115b83a4 100644 --- a/src/tydra/usd-export.cc +++ b/src/tydra/usd-export.cc @@ -58,8 +58,7 @@ static bool FlattenSkelNode(const SkelNode &node, } -// TODO: skelAnimationSource -static bool ExportSkeleton(const SkelHierarchy &skel, Skeleton *dst, std::string *err) { +static bool ExportSkeleton(const SkelHierarchy &skel, const std::string &animSourcePath, Skeleton *dst, std::string *err) { size_t num_joints{0}; CountNodes(skel.root_node, num_joints); @@ -92,6 +91,14 @@ static bool ExportSkeleton(const SkelHierarchy &skel, Skeleton *dst, std::string dst->bindTransforms.set_value(bindTransforms); dst->restTransforms.set_value(restTransforms); + if (animSourcePath.size()) { + Path animSourceTarget(animSourcePath, ""); + Relationship animSourceRel; + animSourceRel.set(animSourceTarget); + // TODO: add `prepend` qualifier? + dst->animationSource = animSourceRel; + } + dst->name = skel.prim_name; return true; @@ -963,7 +970,15 @@ bool export_to_usda(const RenderScene &scene, if ((scene.meshes[i].skel_id > -1) && (size_t(scene.meshes[i].skel_id) < scene.skeletons.size())) { DCOUT("Export Skeleton"); - if (!detail::ExportSkeleton(scene.skeletons[size_t(scene.meshes[i].skel_id)], &skel, err)) { + + const SkelHierarchy &src_skel = scene.skeletons[size_t(scene.meshes[i].skel_id)]; + + std::string src_animsource; // empty = no animationSource + if (src_skel.anim_id > -1) { + src_animsource = "/animations/" + scene.animations[size_t(src_skel.anim_id)].prim_name; + } + + if (!detail::ExportSkeleton(src_skel, src_animsource, &skel, err)) { return false; } @@ -977,12 +992,17 @@ bool export_to_usda(const RenderScene &scene, mesh.skeleton = skelRel; + // Add apiSchemas both for GeomMesh and Skeleton + // // prepend apiSchemas = ["SkelBindingAPI"] + // APISchemas skelAPISchema; skelAPISchema.listOpQual = ListEditQual::Prepend; skelAPISchema.names.push_back({APISchemas::APIName::SkelBindingAPI, ""}); mesh.metas().apiSchemas = skelAPISchema; + skel.metas().apiSchemas = skelAPISchema; + has_skel = true; } @@ -1046,15 +1066,23 @@ bool export_to_usda(const RenderScene &scene, } - for (size_t i = 0; i < scene.animations.size(); i++) { - SkelAnimation skelAnim; - if (!detail::ExportSkelAnimation(scene.animations[i], &skelAnim, err)) { - return false; - } + { + Scope animGroup; + animGroup.name = "animations"; - // TODO: Put SkelAnimation under SkelRoot - Prim prim(skelAnim); - stage.add_root_prim(std::move(prim)); + Prim animGroupPrim(animGroup); + + for (size_t i = 0; i < scene.animations.size(); i++) { + SkelAnimation skelAnim; + if (!detail::ExportSkelAnimation(scene.animations[i], &skelAnim, err)) { + return false; + } + + // TODO: Put SkelAnimation under SkelRoot + Prim prim(skelAnim); + animGroupPrim.add_child(std::move(prim)); + } + stage.add_root_prim(std::move(animGroupPrim)); } {