# Bulge free Dual Quaternion Skinning (Trick)

Left: standard DQS. Right DQS bulge correction (both use same automatic skin weight: "smooth bind" inside Maya).

I provide a C++ code snippet to correct the Dual Quaternion Skinning bulge artifact. This code reproduces the article "Bulging-free dual quaternion skinning"  from Kim, YoungBeom and Han. This trick is fast and easy to implement, however, in specific cases it will produce visible discontinuities in the final mesh deformation.

Here is how the trick works, first we compute (and possibly store) the distance of vertices from their nearest bone segment. While animating, we detect vertices that have got farther away from their bone segment, then we project back towards the bone segment to maintain the original distance. The nearest bone is determine through skinning weights. Note that by construction vertices only bulge around the outside of a bended joint, therefore, only those vertices are altered.

This re-projection trick may introduce visible discontinuity in the mesh deformation and hence in the texture or light reflection. While most joints does not exhibit this problem, especially with coarse mesh, some configuration are more prone to this artifact. On a hand model problems may arise when spreading the fingers. In areas in between the knuckles, neighboring vertices may be projected in opposite directions. This problem is also encountered in the crotch area when doing the split. Left no bulge correction, right bulge correction enabled:

struct Dqs_fix {
// A bone is defined as a segment (origin, direction and length in 3D space)
std::vector<anim::Bone> _bone_rest_pose;
// _weights[vertex Index] == map<bone_index, bone_skinning_weight>
std::vector< std::map<int, float> > _weights;

/// Bulging-free dual quaternion skinning
/// "YoungBeom Kim andJungHyun Han"
inline Vec3 apply(
int id, // Index of the vertex we correct
const Point3& rest_vert, // Vertex position at bind pose / T pose
const Point3& dq_in, // Vertex after dual quaternion skinning
const std::vector<Mat4x4> skinning_transfo) // Global transformation for each joint
{
// find the index of the bone with the highest weight for vertex id
int major_bone = anim::find_max_index(_weights[id]);

// Find out the distance to the bone in rest pose and after skinning.
const anim::Bone& bone_rest = _bone_rest_pose[major_bone];
float dv_rest = bone_rest.dist_from_segment(rest_vert);

const anim::Bone bone_current = bone_rest.transform(skinning_transfo[major_bone]);
float dv_current = bone_current.dist_from_segment(dq_in);

Vec3 result = dq_in;

// If dv has grown then bulging is detected so we draw the vertex towards
// the closest bone point.
if (dv_current > dv_rest)
{
Point3 proj_point = bone_current.project_on_segment(dq_in);
Vec3 proj_dir  = dq_in - proj_point;
const float  factor = dv_rest / dv_current;
result = proj_point + (factor * proj_dir);
}
return result;
}

};

// -------------------------------------------------------------------------

/// Finds the max index in a weight map.
int find_max_index( const std::map<int, float> >& weights)
{
auto it = weights.begin();
auto maxit = weights.begin();
float max = 0.f;
for( ;  it != weights.end(); ++it) {
if (it->second > max)
{
max = it->second;
maxit = it;
}
}
assert(maxit->second == max);
return maxit->first;
}

// -------------------------------------------------------------------------

float Bone::dist_from_segment(const Point3& p) const {
Vec3 op = p - _org;
float x = op.dot(_dir) / (_length * _length);
x = fminf(1.f, fmaxf(0.f, x));
Point3 proj = _org + _dir * x;
float d = proj.distance_squared(p);
return sqrtf(d);
}

// -------------------------------------------------------------------------
Point3 Bone::project_on_segment(const Point3& p) const
{
const Vec3 op = p - _org;
float d = op.dot(_dir.normalized()); // projected dist from origin

if(d < 0)            return _org;
else if(d > _length) return _org + _dir;
else                 return _org + _dir.normalized() * d;
}

## Reference:

Alternative approaches (update)

One comment

hi Sam,
if you want to use it in Maya, you probably want to implement it as a MPxSkinCluster, but you would need to implement all the maya boilerplate code (I/O attributes, etc), make it GPU friendly, etc etc… Furthermore, you’ll need to compile it depending on your maya version and OS, so all in all, it’s probably something you’d need to require from your pipeline team, and not something you want to support as a freelance rigger with several clients.

fruity - 18-12-’21 17:02
(optional field, I won't disclose or spam but it's necessary to notify you if I respond to your comment)
All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.
Anti-spam question: