| Summary: | Quaternion SLERP not working jumping and returning incorrect interpolation | ||
|---|---|---|---|
| Product: | [JogAmp] Jogl | Reporter: | Tek <fwdk98003> |
| Component: | graph | Assignee: | Rami Santina <rami.santina> |
| Status: | RESOLVED FIXED | ||
| Severity: | critical | CC: | gouessej, harvey.harrison, org.jogamp, rami.santina, sgothel |
| Priority: | P4 | ||
| Version: | 2 | ||
| Hardware: | All | ||
| OS: | all | ||
| Type: | --- | SCM Refs: |
2b3bb9426385d97375c3312f5c0f4e2a827b1fbb
|
| Workaround: | --- | ||
Thank you Tek, I hope Rami can solve it. This code looks correct to me. It's derived from: m = ((sin(θ) * (1 - t)) / sin(θ)) * qa n = ((sin(θ) * t) / sin(θ)) * qb q(t) = m + n Where: θ = arcos(qa · qb) According to "Mathematics for 3D Game Programming". Unfortunately, I don't have unit tests for it. (In reply to comment #2) > This code looks correct to me. > > It's derived from: > > m = ((sin(θ) * (1 - t)) / sin(θ)) * qa > n = ((sin(θ) * t) / sin(θ)) * qb > Hmm, something seems off in the above...doesn't that simplify down to simple LERP ((sin(θ) * (1 - t)) / sin(θ)) .. (sin(θ) / sin(θ)) * (1 - t) .. (1 - t) Looking at the link, I think what you wanted was: m = ((sin(θ * (1 - t))) / sin(θ)) * qa n = ((sin(θ * t)) / sin(θ)) * qb Also, the above falls down when sin(θ) == 0 (quarternions aligned or extactly 180 degress apart) Have a look at the following: http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ Which covers the aligned and nearly-opposite cases correctly. Harvey Not compiled, not tested, just the code I have lying around from lunch.
public void slerp(Quaternion a, Quaternion b, float t) {
float cosom = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
float t1 = 1.0f - t;
// if the two quaternions are close, just use linear interpolation
if (cosom >= 0.95f) {
x = a.x * t1 + b.x * t;
y = a.y * t1 + b.y * t;
z = a.z * t1 + b.z * t;
w = a.w * t1 + b.w * t;
return;
}
// the quaternions are nearly opposite, we can pick any axis normal to a,b
// to do the rotation
if (cosom <= -0.99f) {
x = 0.5f * (a.x + b.x);
y = 0.5f * (a.y + b.y);
z = 0.5f * (a.z + b.z);
w = 0.5f * (a.w + b.w);
return;
}
// cosom is now withion range of acos, do a SLERP
float sinom = FloatUtil.sqrt(1.0f - cosom * cosom);
float omega = FloatUtil.acos(cosom);
float scla = (float)FloatUtil.sin(t1 * omega) / sinom;
float sclb = (float)FloatUtil.sin( t * omega) / sinom;
x = a.x * scla + b.x * sclb;
y = a.y * scla + b.y * sclb;
z = a.z * scla + b.z * sclb;
w = a.w * scla + b.w * sclb;
}
(In reply to comment #4) > Not compiled, not tested, just the code I have lying around from lunch. > ... http://jogamp.org/git/?p=jogl.git;a=commit;h=2b3bb9426385d97375c3312f5c0f4e2a827b1fbb Thank you! @Tek: Please test. And if you can, please add a unit tests or two! (Via email .. or better git pull req.) Let me test the code. I'll get back to you in few hours |
It's Tek again with quaternion class. I've been using jogamp.graph.math.Quaternion.slerp() to rotate our model but it seems like slerp() is implemented incorrectly or I am not understanding the math behind it. Currently, the slerp code does the following: if ((1.0f+cosom) > MathFloat.E) { ... } else { x =-a.y; y = a.x; z =-a.w; w = a.z; sclp = MathFloat.sin((1.0f-t) * MathFloat.PI * 0.5f); sclq = MathFloat.sin(t * MathFloat.PI * 0.5f); x = sclp*a.x + sclq*b.x; y = sclp*a.y + sclq*b.y; z = sclp*a.z + sclq*b.z; } I am not sure why anyone would set x value to a.y, y to a.x, z to a.w, and w to a.z. This explains why my quaternion is jumping during animation. I believe "if ((1.0f+cosom) > MathFloat.E)" is trying to see if cosom is negative but I am not sure why only x and z will have opposite of a.y and a.w. Also, angle, w, is not being set at all so that explains why my model doesn't animate until the last frame of display. I am not a Quaternion expert so I don't know the full math behind Quaternion but if I am misinterpreting the slerp(), please let me know. I've implemented a slerp method according to Nick Bobic (http://www.gamasutra.com/view/feature/131686/rotating_objects_using_quaternions.php?page=2) /** Set this quaternion from a Sphereical interpolation * of two param quaternion, used mostly for rotational animation * @param a initial quaternion * @param b target quaternion * @param t float between 0 and 1 representing interp. */ @Override public void slerp(Quaternion a,Quaternion b, float t) { float omega, cosom, sinom, sclp, sclq; cosom = a.getX()*b.getX() + a.getY()*b.getY() + a.getZ()*b.getZ() + a.getW()*b.getW(); //adjust the signs (if necessary) if(cosom<0.0) { cosom = -cosom; b.setX(-b.getX()); b.setY(-b.getY()); b.setZ(-b.getZ()); b.setW(-b.getW()); } // calculate coefficients // standard case (slerp) if ((1.0f-cosom) > MathFloat.E) { omega = (float)MathFloat.acos(cosom); sinom = (float)MathFloat.sin(omega); sclp = (float)MathFloat.sin((1.0f-t)*omega) / sinom; sclq = (float)MathFloat.sin(t*omega) / sinom; } else { // "from" and "to" quaternions are very close // ... so we can do a linear interpolation sclp = 1.0f - t; sclq = t; } //calculate the final values x = sclp*a.getX() + sclq*b.getX(); y = sclp*a.getY() + sclq*b.getY(); z = sclp*a.getZ() + sclq*b.getZ(); w = sclp*a.getW() + sclq*b.getW(); } It's working correctly with my model and it was bit easier to understand. Please advise that I had to override slerp() from a class that extended Quaternion. Therefore, I had to use getters and setters.