From f2d70ede249b90e516539a61c068cc0e21141718 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sat, 20 Apr 2024 08:14:24 +0900 Subject: [PATCH] [Tydra] AnimationChannel refactoring(W.I.P.) --- src/tydra/render-data.cc | 103 ++++++++++----- src/tydra/render-data.hh | 19 ++- src/tydra/usd-export.cc | 267 ++++++++++++++++++++++++++++++++------- 3 files changed, 301 insertions(+), 88 deletions(-) diff --git a/src/tydra/render-data.cc b/src/tydra/render-data.cc index bacf47e7..32f5e53d 100644 --- a/src/tydra/render-data.cc +++ b/src/tydra/render-data.cc @@ -5125,15 +5125,6 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e // 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)); } @@ -5153,6 +5144,18 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e skelAnim.scales.authored() ? "yes" : "no")); } + // + // Reorder values[channels][timeCode][jointId] into values[jointId][channels][timeCode] + // + + std::map> channelMap; + StringAndIdMap jointIdMap; + + for (const auto &joint : joints) { + uint64_t id = jointIdMap.size(); + jointIdMap.add(joint.str(), id); + } + Animatable> translations; if (!skelAnim.translations.get_value(&translations)) { @@ -5169,6 +5172,9 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `scales` attribute of SkelAnimation. Maybe ValueBlock or connection? : {}", abs_path)); } + // + // NOTE: both timeSamples and default value are authored, timeSamples wins. + // bool is_translations_timesamples = false; bool is_rotations_timesamples = false; bool is_scales_timesamples = false; @@ -5181,16 +5187,25 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e } for (const auto &sample : ts_txs.get_samples()) { - AnimationSample> dst; if (!sample.blocked) { // length check if (sample.value.size() != joints.size()) { PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} translations.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path)); } - dst.t = float(sample.t); - dst.value = sample.value; - channel_txs.translations.samples.push_back(dst); + for (size_t j = 0; j < sample.value.size(); j++) { + AnimationSample s; + s.t = float(sample.t); + s.value = sample.value[j]; + + std::string jointName = jointIdMap.at(j); + auto &it = channelMap.at(jointName).at(AnimationChannel::ChannelType::Translation); + if (it.translations.samples.empty()) { + it.type = AnimationChannel::ChannelType::Translation; + } + it.translations.samples.push_back(s); + } + } } is_translations_timesamples = true; @@ -5214,6 +5229,7 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e } } + // value at 'default' time. std::vector translation; std::vector rotation; std::vector scale; @@ -5268,33 +5284,54 @@ bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &e is_scales_timesamples = false; } - // Use USD TimeCode::Default for static sample. - if (is_translations_timesamples) { - } else { - AnimationSample> sample; - sample.t = std::numeric_limits::quiet_NaN(); - sample.value = translation; - channel_txs.translations.samples.push_back(sample); + if (!is_translations_timesamples) { + // Create a channel value with single-entry + // Use USD TimeCode::Default for static sample. + for (const auto &joint : joints) { + channelMap[joint.str()][AnimationChannel::ChannelType::Translation].type = AnimationChannel::ChannelType::Translation; + + AnimationSample s; + s.t = std::numeric_limits::quiet_NaN(); + uint64_t joint_id = jointIdMap.at(joint.str()); + s.value = translation[joint_id]; + channelMap[joint.str()][AnimationChannel::ChannelType::Translation].translations.samples.clear(); + channelMap[joint.str()][AnimationChannel::ChannelType::Translation].translations.samples.push_back(s); + } } - if (is_rotations_timesamples) { - } else { - AnimationSample> sample; - sample.t = std::numeric_limits::quiet_NaN(); - sample.value = rotation; - channel_rots.rotations.samples.push_back(sample); + if (!is_rotations_timesamples) { + 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()); + 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) { - } else { - AnimationSample> sample; - sample.t = std::numeric_limits::quiet_NaN(); - sample.value = scale; - channel_scales.scales.samples.push_back(sample); + if (!is_scales_timesamples) { + for (const auto &joint : joints) { + channelMap[joint.str()][AnimationChannel::ChannelType::Scale].type = AnimationChannel::ChannelType::Scale; + + AnimationSample s; + s.t = std::numeric_limits::quiet_NaN(); + uint64_t joint_id = jointIdMap.at(joint.str()); + s.value = scale[joint_id]; + channelMap[joint.str()][AnimationChannel::ChannelType::Scale].scales.samples.clear(); + channelMap[joint.str()][AnimationChannel::ChannelType::Scale].scales.samples.push_back(s); + } } - PUSH_ERROR_AND_RETURN("TODO"); + anim_out->abs_path = abs_path.full_path_name(); + anim_out->prim_name = skelAnim.name; + anim_out->display_name = skelAnim.metas().displayName.value_or(""); + + anim_out->channels_map = std::move(channelMap); + return true; } bool RenderSceneConverter::BuildNodeHierarchyImpl( diff --git a/src/tydra/render-data.hh b/src/tydra/render-data.hh index 398c193c..d6e9fa51 100644 --- a/src/tydra/render-data.hh +++ b/src/tydra/render-data.hh @@ -665,6 +665,11 @@ struct AnimationSampler { 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. @@ -672,12 +677,12 @@ struct AnimationChannel { // Matrix precision is reduced to float-precision // NOTE: transform is not supported in glTF(you need to decompose transform // matrix into TRS) - AnimationSampler> transforms; + AnimationSampler transforms; - AnimationSampler> translations; - AnimationSampler> rotations; // Rotation is represented as quaternions - AnimationSampler> scales; // half-types are upcasted to float precision - AnimationSampler> weights; + AnimationSampler translations; + AnimationSampler rotations; // Rotation is represented as quaternions + AnimationSampler scales; // half-types are upcasted to float precision + AnimationSampler weights; //std::string joint_name; // joint name(UsdSkel::joints) //int64_t joint_id{-1}; // joint index in SkelHierarchy @@ -689,8 +694,8 @@ struct Animation { std::string abs_path; // Target USD Prim path std::string display_name; // `displayName` prim meta - // key = joint, value = channels(Usually 3(trans, rots and scales)) - std::map> channels_map; + // key = joint, value = (key: channel_type, value: channel_value) + std::map> channels_map; }; struct Node { diff --git a/src/tydra/usd-export.cc b/src/tydra/usd-export.cc index 3a3e528b..d9a7bf73 100644 --- a/src/tydra/usd-export.cc +++ b/src/tydra/usd-export.cc @@ -83,68 +83,239 @@ static bool ExportSkelAnimation(const Animation &anim, SkelAnimation *dst, std:: for (const auto &channels : anim.channels_map) { joints[joint_idMap.at(channels.first)] = value::token(channels.first); } + dst->joints = joints; + + bool no_tx_channel{true}; + bool no_rot_channel{true}; + bool no_scale_channel{true}; + + bool all_joints_has_tx_channel{true}; + bool all_joints_has_rot_channel{true}; + bool all_joints_has_scale_channel{true}; for (const auto &channels : anim.channels_map) { - size_t tx_id{~0u}; - size_t rot_id{~0u}; - size_t scale_id{~0u}; - - for (size_t i = 0; i < channels.second.size(); i++) { - if (channels.second[i].type == AnimationChannel::ChannelType::Translation) { - tx_id = i; - } else if (channels.second[i].type == AnimationChannel::ChannelType::Rotation) { - rot_id = i; - } else if (channels.second[i].type == AnimationChannel::ChannelType::Scale) { - scale_id = i; + + bool has_tx_channel; + bool has_rot_channel; + bool has_scale_channel; + + has_tx_channel = channels.second.count(AnimationChannel::ChannelType::Translation); + has_rot_channel = channels.second.count(AnimationChannel::ChannelType::Rotation); + has_scale_channel = channels.second.count(AnimationChannel::ChannelType::Scale); + + if (has_tx_channel) { + no_tx_channel = false; + } else { + all_joints_has_tx_channel = false; + } + + if (has_rot_channel) { + no_rot_channel = false; + } else { + all_joints_has_rot_channel = false; + } + + if (has_scale_channel) { + no_scale_channel = false; + } else { + all_joints_has_scale_channel = false; + } + } + + if (!no_tx_channel && !all_joints_has_tx_channel) { + PUSH_ERROR_AND_RETURN("translation channel partially exists among joints. No joints have animation channel or all joints have animation channels."); + } + + if (!no_rot_channel && !all_joints_has_rot_channel) { + PUSH_ERROR_AND_RETURN("rotation channel partially exists among joints. No joints have animation channel or all joints have animation channels."); + } + + if (!no_scale_channel && !all_joints_has_scale_channel) { + PUSH_ERROR_AND_RETURN("scale channel partially exists among joints. No joints have animation channel or all joints have animation channels."); + } + + if (no_tx_channel) { + // Author static(default) value. + std::vector translations; + translations.assign(joints.size(), {1.0f, 1.0f, 1.0f}); + + dst->translations.set_value(translations); + } else { + + // All joints should have same timeCode. + // First collect timeCodes + std::unordered_set timeCodes; + + for (const auto &channels : anim.channels_map) { + + const auto &tx_it = channels.second.find(AnimationChannel::ChannelType::Translation); + if (tx_it != channels.second.end()) { + for (size_t t = 0; t < tx_it->second.translations.samples.size(); t++) { + timeCodes.insert(tx_it->second.translations.samples[t].t); + } } + } - // TODO: Provide dummy value when missing. - if ((tx_id == ~0u) || (rot_id == ~0u) || (scale_id == ~0u)) { - PUSH_ERROR_AND_RETURN(fmt::format("translation, rotation and scale AnimationChannel must be all provided. translation {}, rotation {}, scale {}", - (tx_id == ~0u) ? "missing" : "provided", - (rot_id == ~0u) ? "missing" : "provided", - (scale_id == ~0u) ? "missing" : "provided")); + // key: timeCode. value: values for each joints. + std::map> ts_txs; + for (const auto &tc : timeCodes) { + ts_txs[double(tc)].resize(joints.size()); } -#if 0 // TODO - const AnimationChannel &channel = anim.channels[i]; - - if (channel.rotations.samples.size()) { - Animatable> rots; - for (const auto &sample : channel.rotations.samples) { - std::vector ts(sample.value.size()); - for (size_t k = 0; k < sample.value.size(); k++) { - ts[k][0] = sample.value[k][0]; - ts[k][1] = sample.value[k][1]; - ts[k][2] = sample.value[k][2]; - ts[k][3] = sample.value[k][3]; + // Pack channel values + for (const auto &channels : anim.channels_map) { + + const auto &tx_it = channels.second.find(AnimationChannel::ChannelType::Translation); + if (tx_it != channels.second.end()) { + + for (size_t t = 0; t < tx_it->second.translations.samples.size(); t++) { + float tc = tx_it->second.translations.samples[t].t; + if (!timeCodes.count(tc)) { + PUSH_ERROR_AND_RETURN(fmt::format("All animation channels have same timeCodes. timeCode {} is only seen in `translation` animation channel {}", tc, channels.first)); + } + uint64_t joint_id = joint_idMap.at(channels.first); + + std::vector &txs = ts_txs.at(double(tc)); + // just in case + if (joint_id > txs.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("Internal error. joint_id {} exceeds # of joints {}", joint_id, txs.size())); + } + txs[size_t(joint_id)] = tx_it->second.translations.samples[t].value; } - rots.add_sample(double(sample.t), ts); } - dst->rotations.set_value(rots); - } else if (channel.translations.samples.size()) { - Animatable> txs; - for (const auto &sample : channel.translations.samples) { - txs.add_sample(double(sample.t), sample.value); + } + + Animatable> ts; + for (const auto &s : ts_txs) { + ts.add_sample(s.first, s.second); + } + + dst->translations.set_value(ts); + } + + if (no_rot_channel) { + // Author static(default) value. + std::vector rots; + value::quatf q; + q.imag = {0.0f, 0.0f, 0.0f}; + q.real = 1.0f; + rots.assign(joints.size(), q); + + dst->rotations.set_value(rots); + + } else { + + std::unordered_set timeCodes; + + for (const auto &channels : anim.channels_map) { + + const auto &rot_it = channels.second.find(AnimationChannel::ChannelType::Rotation); + if (rot_it != channels.second.end()) { + for (size_t t = 0; t < rot_it->second.rotations.samples.size(); t++) { + timeCodes.insert(rot_it->second.rotations.samples[t].t); + } + } + + } + + std::map> ts_rots; + for (const auto &tc : timeCodes) { + ts_rots[double(tc)].resize(joints.size()); + } + + for (const auto &channels : anim.channels_map) { + + const auto &rot_it = channels.second.find(AnimationChannel::ChannelType::Rotation); + if (rot_it != channels.second.end()) { + + for (size_t t = 0; t < rot_it->second.rotations.samples.size(); t++) { + float tc = rot_it->second.rotations.samples[t].t; + if (!timeCodes.count(tc)) { + PUSH_ERROR_AND_RETURN(fmt::format("All animation channels have same timeCodes. timeCode {} is only seen in `rotation` animation channel {}", tc, channels.first)); + } + uint64_t joint_id = joint_idMap.at(channels.first); + + std::vector &rots = ts_rots.at(double(tc)); + value::quatf v; + v[0] = rot_it->second.rotations.samples[t].value[0]; + v[1] = rot_it->second.rotations.samples[t].value[1]; + v[2] = rot_it->second.rotations.samples[t].value[2]; + v[3] = rot_it->second.rotations.samples[t].value[3]; + rots[size_t(joint_id)] = v; + } + } + } + + Animatable> ts; + for (const auto &s : ts_rots) { + ts.add_sample(s.first, s.second); + } + + dst->rotations.set_value(ts); + } + + if (no_scale_channel) { + // Author static(default) value. + std::vector scales; + scales.assign(joints.size(), {value::float_to_half_full(1.0f), value::float_to_half_full(1.0f), value::float_to_half_full(1.0f)}); + + dst->scales.set_value(scales); + + } else { + std::unordered_set timeCodes; + + for (const auto &channels : anim.channels_map) { + + const auto &scale_it = channels.second.find(AnimationChannel::ChannelType::Scale); + if (scale_it != channels.second.end()) { + for (size_t t = 0; t < scale_it->second.scales.samples.size(); t++) { + timeCodes.insert(scale_it->second.scales.samples[t].t); + } } - dst->translations.set_value(txs); - } else if (channel.scales.samples.size()) { - Animatable> scales; - for (const auto &sample : channel.scales.samples) { - std::vector ts(sample.value.size()); - for (size_t k = 0; k < sample.value.size(); k++) { - ts[k][0] = tinyusdz::value::float_to_half_full(sample.value[k][0]); - ts[k][1] = tinyusdz::value::float_to_half_full(sample.value[k][1]); - ts[k][2] = tinyusdz::value::float_to_half_full(sample.value[k][2]); + + } + + std::map> ts_scales; + for (const auto &tc : timeCodes) { + ts_scales[double(tc)].resize(joints.size()); + } + + for (const auto &channels : anim.channels_map) { + + const auto &scale_it = channels.second.find(AnimationChannel::ChannelType::Scale); + if (scale_it != channels.second.end()) { + + for (size_t t = 0; t < scale_it->second.scales.samples.size(); t++) { + float tc = scale_it->second.scales.samples[t].t; + if (!timeCodes.count(tc)) { + PUSH_ERROR_AND_RETURN(fmt::format("All animation channels have same timeCodes. timeCode {} is only seen in `scale` animation channel {}", tc, channels.first)); + } + uint64_t joint_id = joint_idMap.at(channels.first); + + std::vector &scales = ts_scales.at(double(tc)); + value::half3 v; + v[0] = value::float_to_half_full(scale_it->second.scales.samples[t].value[0]); + v[1] = value::float_to_half_full(scale_it->second.scales.samples[t].value[1]); + v[2] = value::float_to_half_full(scale_it->second.scales.samples[t].value[2]); + scales[size_t(joint_id)] = v; } - scales.add_sample(double(sample.t), ts); } - dst->scales.set_value(scales); } -#endif + + Animatable> ts; + for (const auto &s : ts_scales) { + ts.add_sample(s.first, s.second); + } + + dst->scales.set_value(ts); + } + + dst->name = anim.prim_name; + if (anim.display_name.size()) { + dst->metas().displayName = anim.display_name; } - return false; + return true; } static bool ToGeomMesh(const RenderMesh &rmesh, GeomMesh *dst, std::string *err) {