Fix #116615: Store Blender bone lengths on USD Skeletons
USD Skeletons (armatures) are joint-based rather than bone-based in construction. This means that there's no native bone concept nor is there any bone lengths. Currently, Blender's USD import uses an estimation, roughly corresponding to the average distance between joints in the entire Skeleton, to set the bone length in situations where it's not obvious that 2 joints are directly connected. This is imperfect but shouldn't affect actual functioning of the rig. For armatures coming from external software this is probably the best we can do. However, for armatures originating from Blender, we can use a custom primvar to store the bone lengths directly. This allows Blender armatures to be exported and re-imported in much better fidelity. This is superior to prior techniques, like those employed by FBX, which alter the actual Skeleton, inserting extra joints where unnecessary. Not only is this detrimental when using these files in external software, but it's still imperfect when importing back into Blender too. Pull Request: https://projects.blender.org/blender/blender/pulls/126954
This commit is contained in:
committed by
Jesse Yurkovich
parent
d33e708343
commit
832d243249
@@ -21,6 +21,9 @@ struct Object;
|
||||
|
||||
namespace blender::io::usd {
|
||||
|
||||
/* Custom Blender Primvar name used for storing armature bone lengths. */
|
||||
inline const pxr::TfToken BlenderBoneLengths("blender:bone_lengths", pxr::TfToken::Immortal);
|
||||
|
||||
/**
|
||||
* Recursively invoke the given function on the given armature object's bones.
|
||||
* This function is a no-op if the object isn't an armature.
|
||||
|
||||
@@ -848,66 +848,84 @@ void import_skeleton(Main *bmain,
|
||||
}
|
||||
}
|
||||
|
||||
float avg_len_scale = 0;
|
||||
for (size_t i = 0; i < num_joints; ++i) {
|
||||
/* Use our custom bone length data if possible, otherwise fallback to estimated lengths. */
|
||||
const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(skel.GetPrim());
|
||||
const pxr::UsdGeomPrimvar pv_lengths = pv_api.GetPrimvar(BlenderBoneLengths);
|
||||
if (pv_lengths.HasValue()) {
|
||||
pxr::VtArray<float> bone_lengths;
|
||||
pv_lengths.ComputeFlattened(&bone_lengths);
|
||||
|
||||
/* If the bone has any children, scale its length
|
||||
* by the distance between this bone's head
|
||||
* and the average head location of its children. */
|
||||
for (size_t i = 0; i < num_joints; ++i) {
|
||||
EditBone *bone = edit_bones[i];
|
||||
pxr::GfVec3f head(bone->head);
|
||||
pxr::GfVec3f tail(bone->tail);
|
||||
|
||||
if (child_bones[i].is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EditBone *parent = edit_bones[i];
|
||||
if (!parent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::GfVec3f avg_child_head(0);
|
||||
for (int j : child_bones[i]) {
|
||||
EditBone *child = edit_bones[j];
|
||||
if (!child) {
|
||||
continue;
|
||||
}
|
||||
pxr::GfVec3f child_head(child->head);
|
||||
avg_child_head += child_head;
|
||||
}
|
||||
|
||||
avg_child_head /= child_bones[i].size();
|
||||
|
||||
pxr::GfVec3f parent_head(parent->head);
|
||||
pxr::GfVec3f parent_tail(parent->tail);
|
||||
|
||||
const float new_len = (avg_child_head - parent_head).GetLength();
|
||||
|
||||
/* Check for epsilon relative to the parent head before scaling. */
|
||||
if (new_len > .00001 * max_mag_component(parent_head)) {
|
||||
parent_tail = parent_head + (parent_tail - parent_head).GetNormalized() * new_len;
|
||||
copy_v3_v3(parent->tail, parent_tail.data());
|
||||
avg_len_scale += new_len;
|
||||
tail = head + (tail - head).GetNormalized() * bone_lengths[i];
|
||||
copy_v3_v3(bone->tail, tail.data());
|
||||
}
|
||||
}
|
||||
else {
|
||||
float avg_len_scale = 0;
|
||||
for (size_t i = 0; i < num_joints; ++i) {
|
||||
|
||||
/* Scale terminal bones by the average length scale. */
|
||||
avg_len_scale /= num_joints;
|
||||
/* If the bone has any children, scale its length
|
||||
* by the distance between this bone's head
|
||||
* and the average head location of its children. */
|
||||
|
||||
for (size_t i = 0; i < num_joints; ++i) {
|
||||
if (!child_bones[i].is_empty()) {
|
||||
/* Not a terminal bone. */
|
||||
continue;
|
||||
if (child_bones[i].is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EditBone *parent = edit_bones[i];
|
||||
if (!parent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxr::GfVec3f avg_child_head(0);
|
||||
for (int j : child_bones[i]) {
|
||||
EditBone *child = edit_bones[j];
|
||||
if (!child) {
|
||||
continue;
|
||||
}
|
||||
pxr::GfVec3f child_head(child->head);
|
||||
avg_child_head += child_head;
|
||||
}
|
||||
|
||||
avg_child_head /= child_bones[i].size();
|
||||
|
||||
pxr::GfVec3f parent_head(parent->head);
|
||||
pxr::GfVec3f parent_tail(parent->tail);
|
||||
|
||||
const float new_len = (avg_child_head - parent_head).GetLength();
|
||||
|
||||
/* Check for epsilon relative to the parent head before scaling. */
|
||||
if (new_len > .00001 * max_mag_component(parent_head)) {
|
||||
parent_tail = parent_head + (parent_tail - parent_head).GetNormalized() * new_len;
|
||||
copy_v3_v3(parent->tail, parent_tail.data());
|
||||
avg_len_scale += new_len;
|
||||
}
|
||||
}
|
||||
EditBone *bone = edit_bones[i];
|
||||
if (!bone) {
|
||||
continue;
|
||||
}
|
||||
pxr::GfVec3f head(bone->head);
|
||||
|
||||
/* Check for epsilon relative to the head before scaling. */
|
||||
if (avg_len_scale > .00001 * max_mag_component(head)) {
|
||||
pxr::GfVec3f tail(bone->tail);
|
||||
tail = head + (tail - head).GetNormalized() * avg_len_scale;
|
||||
copy_v3_v3(bone->tail, tail.data());
|
||||
/* Scale terminal bones by the average length scale. */
|
||||
avg_len_scale /= num_joints;
|
||||
|
||||
for (size_t i = 0; i < num_joints; ++i) {
|
||||
if (!child_bones[i].is_empty()) {
|
||||
/* Not a terminal bone. */
|
||||
continue;
|
||||
}
|
||||
EditBone *bone = edit_bones[i];
|
||||
if (!bone) {
|
||||
continue;
|
||||
}
|
||||
pxr::GfVec3f head(bone->head);
|
||||
|
||||
/* Check for epsilon relative to the head before scaling. */
|
||||
if (avg_len_scale > .00001 * max_mag_component(head)) {
|
||||
pxr::GfVec3f tail(bone->tail);
|
||||
tail = head + (tail - head).GetNormalized() * avg_len_scale;
|
||||
copy_v3_v3(bone->tail, tail.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <pxr/base/gf/matrix4d.h>
|
||||
#include <pxr/base/gf/matrix4f.h>
|
||||
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
||||
#include <pxr/usd/usdSkel/animation.h>
|
||||
#include <pxr/usd/usdSkel/bindingAPI.h>
|
||||
#include <pxr/usd/usdSkel/skeleton.h>
|
||||
@@ -54,6 +55,7 @@ static void initialize(const Object *obj,
|
||||
using namespace blender::io::usd;
|
||||
|
||||
pxr::VtTokenArray joints;
|
||||
pxr::VtArray<float> bone_lengths;
|
||||
pxr::VtArray<pxr::GfMatrix4d> bind_xforms;
|
||||
pxr::VtArray<pxr::GfMatrix4d> rest_xforms;
|
||||
|
||||
@@ -69,6 +71,9 @@ static void initialize(const Object *obj,
|
||||
return;
|
||||
}
|
||||
|
||||
/* Store Blender bone lengths to facilitate better roundtripping. */
|
||||
bone_lengths.push_back(bone->length);
|
||||
|
||||
joints.push_back(build_usd_joint_path(bone, allow_unicode));
|
||||
const pxr::GfMatrix4f arm_mat(bone->arm_mat);
|
||||
bind_xforms.push_back(pxr::GfMatrix4d(arm_mat));
|
||||
@@ -93,7 +98,15 @@ static void initialize(const Object *obj,
|
||||
skel.GetBindTransformsAttr().Set(bind_xforms);
|
||||
skel.GetRestTransformsAttr().Set(rest_xforms);
|
||||
|
||||
pxr::UsdSkelBindingAPI usd_skel_api = pxr::UsdSkelBindingAPI::Apply(skel.GetPrim());
|
||||
const pxr::UsdPrim skel_prim = skel.GetPrim();
|
||||
|
||||
/* Store the custom bone lengths as just a regular Primvar attached to the Skeleton. */
|
||||
const pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(skel_prim);
|
||||
pxr::UsdGeomPrimvar pv_lengths = pv_api.CreatePrimvar(
|
||||
BlenderBoneLengths, pxr::SdfValueTypeNames->FloatArray, pxr::UsdGeomTokens->uniform);
|
||||
pv_lengths.Set(bone_lengths);
|
||||
|
||||
pxr::UsdSkelBindingAPI usd_skel_api = pxr::UsdSkelBindingAPI::Apply(skel_prim);
|
||||
|
||||
if (skel_anim) {
|
||||
usd_skel_api.CreateAnimationSourceRel().SetTargets(
|
||||
|
||||
Reference in New Issue
Block a user