Code: Select all
else {
y = atan2(2.0*(q01+q23),(q00+q33)-(q11+q22));
p = qd2 > 1.0 ? D_PI/2.0 : (qd2 < -1.0 ? -D_PI/2.0 : asin (qd2));
r = atan2(2.0*(q12+q03),(q22+q33)-(q00+q11));
}
As I suspected the code that treats the normal condition is executed instead of the code that treats the gimbal lock condition! OK, so the problem is in the 2 tests if(fabs(qd2-1.0) < DBL_EPSILON) and if(fabs(qd2+1.0) < DBL_EPSILON).
I replaced DBL_EPSILON with a double toll variable:
Code: Select all
void Rotation::getYawPitchRoll(double& y, double& p, double& r) const
{
double q00 = quat[0]*quat[0];
double q11 = quat[1]*quat[1];
double q22 = quat[2]*quat[2];
double q33 = quat[3]*quat[3];
double q01 = quat[0]*quat[1];
double q02 = quat[0]*quat[2];
double q03 = quat[0]*quat[3];
double q12 = quat[1]*quat[2];
double q13 = quat[1]*quat[3];
double q23 = quat[2]*quat[3];
double qd2 = 2.0*(q13-q02);
double toll=1.0e-15;
printf ("toll=%g\n",toll);
// handle gimbal lock
if (fabs(qd2-1.0) < toll) {
// north pole
printf("gimbal lock +90°\n");
y = 0.0;
p = M_PI/2.0;
r = 2.0 * atan2(quat[0],quat[3]);
}
else if (fabs(qd2+1.0) < toll) {
// south pole
printf("gimbal lock -90°\n");
y = 0.0;
p = -M_PI/2.0;
r = -2.0 * atan2(quat[0],quat[3]);
}
else {
printf("normal condition\n");
y = atan2(2.0*(q01+q23),(q00+q33)-(q11+q22));
p = qd2 > 1.0 ? M_PI/2.0 : (qd2 < -1.0 ? -M_PI/2.0 : asin (qd2));
r = atan2(2.0*(q12+q03),(q22+q33)-(q00+q11));
}
// convert to degree
y = (y/M_PI)*180;
p = (p/M_PI)*180;
r = (r/M_PI)*180;
}
Recently a new piece of code has been merged in Rotation.cpp (from Realthunder, from OCCT gp/gp_Quaternion.cxx) that implements the conversion between quaternions and generalized Euler angles, in Rotation::getEulerAngles(...) a tolerance 16*DBL_EPSILON is used. I tested this by setting in my code toll=16*DBL_EPSILON and also in this case the correct part of the code is executed.