PDA

View Full Version : Wrong Blending of 4 Dual Quaternions

biller23
01-12-2015, 05:19 PM
Hi. I'm trying to move from matrices to dual quaternions (for skinning in Vertex Shader), but I'm in stuck.. in trouble.. Really I don't know where the problem reside.
Look at this comparison (matrices vs dual quaternions): http://imgur.com/a/iX8AA
ALL the dual quaternions uploaded (I'm sure) are normalized. I upload the dual quaternions as an array of mat2x4 uniforms.
Here's the code that DO NOT works:

vec4 w = in_Weights;
mat2x4 dq0 = mat2x4(bonesParams[in_Bones.x]);
mat2x4 dq1 = mat2x4(bonesParams[in_Bones.y]);
mat2x4 dq2 = mat2x4(bonesParams[in_Bones.z]);
mat2x4 dq3 = mat2x4(bonesParams[in_Bones.w]);

if (dot(dq0[0], dq1[0]) <= 0.0) dq1*= -1; // Antipodality correction.
if (dot(dq0[0], dq2[0]) <= 0.0) dq2*= -1;
if (dot(dq0[0], dq3[0]) <= 0.0) dq3*= -1;
mat2x4 blendedDQ = w.x * dq0;
blendedDQ += w.y * dq1;
blendedDQ += w.z * dq2;
blendedDQ += w.w * dq3;
float invLen = inversesqrt ( dot(blendedDQ[0], blendedDQ[0] );
mat3 skinRot = invLen * q_getMat3( blendedDQ[0] );
vec3 skinTra = invLen * dq_getTranslation( blendedDQ[0], blendedDQ[1] );

In order to test that the q_getMat3() and dq_getTranslation() routines are correct, I made the following modification, converting all 4 quaternion and dual part to matrices and vectors, and blending them instead (and this give me correct results but is naturally slower ):

mat3 m = q_getMat3(dq0[0]) * w.x + q_getMat3(dq1[0]) * w.y + q_getMat3(dq2[0]) * w.z + q_getMat3(dq3[0]) * w.w;
vec3 t = dq_getTranslation(dq0[0], dq0[1]) * w.x + dq_getTranslation(dq1[0], dq1[1]) * w.y +
dq_getTranslation(dq2[0], dq2[1]) * w.z + dq_getTranslation(dq3[0], dq3[1]) * w.w;
skinRot = m;
skinTra = t;

Dual Quaternions should be the efficient way to blend rigid transform but I'm missing some piece of the puzzle.
Any help is appreciated. Thanks in advance.

Dark Photon
01-12-2015, 06:30 PM
float invLen = inversesqrt ( dot(blendedDQ[0], blendedDQ[0] );
mat3 skinRot = invLen * q_getMat3( blendedDQ[0] );
vec3 skinTra = invLen * dq_getTranslation( blendedDQ[0], blendedDQ[1] );

Your normalizing should be applied to the dual quats themselves. Try:

float invLen = inversesqrt ( dot(blendedDQ[0], blendedDQ[0] );
blendedDQ[0] *= invLen;
blendedDQ[1] *= invLen;
mat3 skinRot = q_getMat3( blendedDQ[0] );
vec3 skinTra = dq_getTranslation( blendedDQ[0], blendedDQ[1] );

biller23
01-13-2015, 03:14 AM
Thanks, I tried as you suggested but nothing changed at all. If this is the right algorithm to blend Dual Quaternions, maybe I'm doing something wrong elsewhere.

This are my quaternion / dual quaternion routines:

vec4 q_matrixToQuat(mat3 m){
vec4 q;
if ( m[0].x + m[1].y + m[2].z > 0.0f ) {
float t = + m[0].x + m[1].y + m[2].z + 1.0f;
float s = inversesqrt( t ) * 0.5f;
q[3] = s * t;
q[2] = ( m[0].y - m[1].x ) * s;
q[1] = ( m[2].x - m[0].z ) * s;
q[0] = ( m[1].z - m[2].y ) * s;
} else if ( m[0].x > m[1].y && m[0].x > m[2].z ) {
float t = + m[0].x - m[1].y - m[2].z + 1.0f;
float s = inversesqrt( t ) * 0.5f;
q[0] = s * t;
q[1] = ( m[0].y + m[1].x ) * s;
q[2] = ( m[2].x + m[0].z ) * s;
q[3] = ( m[1].z - m[2].y ) * s;
} else if ( m[1].y > m[2].z ) {
float t = - m[0].x + m[1].y - m[2].z + 1.0f;
float s = inversesqrt( t ) * 0.5f;
q[1] = s * t;
q[0] = ( m[0].y + m[1].x ) * s;
q[3] = ( m[2].x - m[0].z ) * s;
q[2] = ( m[1].z + m[2].y ) * s;
} else {
float t = - m[0].x - m[1].y + m[2].z + 1.0f;
float s = inversesqrt( t ) * 0.5f;
q[2] = s * t;
q[3] = ( m[0].y - m[1].x ) * s;
q[0] = ( m[2].x + m[0].z ) * s;
q[1] = ( m[1].z + m[2].y ) * s;
}
return q;
}

mat3 q_quatToMatrix(vec4 q){
return mat3( 1.0 - 2.0*(q.y*q.y + q.z*q.z), 2.0*((q.x*q.y) + q.z*q.w), 2.0*((q.x*q.z) - q.y*q.w),
2.0*((q.x*q.y) - q.z*q.w), 1.0-2.0* (q.x*q.x + q.z*q.z), 2.0*((q.y*q.z) + q.x*q.w),
2.0*((q.x*q.z) + q.y*q.w), 2.0* ((q.y*q.z) - q.x*q.w), 1.0-2.0* (q.x*q.x + q.y*q.y) );
}

C++ code Translation to Dual Part:

dualPart[0] = -0.5f*( translation[0]*unit_quat[1] + translation[1]*unit_quat[2] + translation[2]*unit_quat[3]);
dualPart[1] = 0.5f*( translation[0]*unit_quat[0] + translation[1]*unit_quat[3] - translation[2]*unit_quat[2]);
dualPart[2] = 0.5f*(-translation[0]*unit_quat[3] + translation[1]*unit_quat[0] + translation[2]*unit_quat[1]);
dualPart[3] = 0.5f*( translation[0]*unit_quat[2] - translation[1]*unit_quat[1] + translation[2]*unit_quat[0]);

Extract translation from Dual Part:

// dual quaternion
vec3 dq_getTranslation( vec4 real, vec4 dual ){
return vec3( 2.0*(-dual[0]*real[1] + dual[1]*real[0] - dual[2]*real[3] + dual[3]*real[2]),
2.0*(-dual[0]*real[2] + dual[1]*real[3] + dual[2]*real[0] - dual[3]*real[1]),
2.0*(-dual[0]*real[3] - dual[1]*real[2] + dual[2]*real[1] + dual[3]*real[0]));
}

biller23
01-14-2015, 05:37 AM
I solved the problem.
I feel so stupid. The code I used for store and retrieve the dual part was flawed: the quaternions are supposed to be in Wxyz form in memory!

Her's the corrected code:

// dual part from translation , quaternion as [x, y, z, w] w scalar part
dualPart[0] = 0.5f*( translation[0]*unit_quat[3] + translation[1]*unit_quat[2] - translation[2]*unit_quat[1]);
dualPart[1] = 0.5f*(-translation[0]*unit_quat[2] + translation[1]*unit_quat[3] + translation[2]*unit_quat[0]);
dualPart[2] = 0.5f*( translation[0]*unit_quat[1] - translation[1]*unit_quat[0] + translation[2]*unit_quat[3]);
dualPart[3] = -0.5f*( translation[0]*unit_quat[0] + translation[1]*unit_quat[1] + translation[2]*unit_quat[2]);

vec3 dq_getTranslation( vec4 real, vec4 dual ){
return vec3( 2.0*(-dual[3]*real[0] + dual[0]*real[3] - dual[1]*real[2] + dual[2]*real[1]),
2.0*(-dual[3]*real[1] + dual[0]*real[2] + dual[1]*real[3] - dual[2]*real[0]),
2.0*(-dual[3]*real[2] - dual[0]*real[1] + dual[1]*real[0] + dual[2]*real[3]));
}

Dark Photon
01-14-2015, 05:42 AM
I solved the problem.
I feel so stupid. The code I used for store and retrieve the dual part was flawed: the quaternions are supposed to be in Wxyz form in memory!

Good catch! Yeah, I remember hitting that too. One thread (https://www.opengl.org/discussion_boards/showthread.php/179306-quaternion-indices) on this very issue.