I'm using quaternions to rotate an object in OpenGL, but I'm having an issue where when I rotate from 5 degrees to -5 degrees (crossing a unit plane) I see my model spin "the long way." That is to say, it spins the full 350 degrees from 5 to -5 instead of the 10 degrees across the plane. This happens for rotations about each axis, anytime I "pass go" by rotating through 0 degrees, and I'm not sure how to fix it.
My code looks like this, where roll, pitch and yaw represent the Euler angles for my object's rotation:
quaternion[0] = cos(pitch/2)*cos(yaw/2)*cos(roll/2) + sin(pitch/2)*sin(yaw/2)*sin(roll/2);
quaternion[1] = sin(pitch/2)*cos(yaw/2)*cos(roll/2) - cos(pitch/2)*sin(yaw/2)*sin(roll/2);
quaternion[2] = cos(pitch/2)*sin(yaw/2)*cos(roll/2) + sin(pitch/2)*cos(yaw/2)*sin(roll/2);
quaternion[3] = cos(pitch/2)*cos(yaw/2)*sin(roll/2) - sin(pitch/2)*sin(yaw/2)*cos(roll/2);
glLoadIdentity();
glPushMatrix();
glRotatef((float) (2.0f * acos(quaternion[0]) * 180.0f / PI), quaternion[1], quaternion[2], quaternion[3]);
Any suggestions?
The problem are not your quaternions or the glRotate(), it is where you calculate the step distance for a rotation.
Possibly you are using a normalization of some kind, where a negative angle like -5 gets "corrected" to a value in the range 0..359, so it will become 355 and your rotation is then performed from 5 to 355.
Related
I'm trying to use quaternions to do rotation animation.
My algorithm creates Quaternions, and slerps every frame.
Here is my code to construct a quaternion by the axis and the rotation angle.
template <typename U>
Quaternion(Vector3<U> vec, const float& angle)
{
vec.normalize();
float cosa = cos(angle/2);
float sina = sin(angle/2);
w = cosa;
x = sina * vec.x;
y = sina * vec.y;
z = sina * vec.z;
}
Then I found that when I tried to rotate 4π radians, the animation does not work because the quaternion I created is equivalent to 0 degrees.
I wonder if quaternions can represent rotations over 360 degrees? Or is my animation algorithm in need of improvement?
I wonder if quaternions can represent rotations over 360 degrees?
No, it can not.
Quaternions between the range [360;720] will treated as rotations at the other direction: [-360;0].
And quaternions between the range [720*k; 720*(k+1)] will be treated as rotations [0;720].
If you use slerp for this kind of animation, quaternions are not good for them.
Quaternions can only slerp between angles which are smaller than 360.
If you still want to do this, use a different representation, like axis-angle.
Rotating be 360 degrees is the same as rotating by 0 degrees. To rotate by an angle alpha bigger than 360 simply rotate by alpha-360 or more general by alpha % 360.
(360 used as synonym for 2pi, you need to take care about degree vs radians of course. And not sure if thats a typo, but 360 degree is 2pi not 4pi)
PS: Actually I think there is nothing wrong with your code, and maybe you dont have to change anything. It's just your expectations that were wrong: You should get the same for a rotation by 4pi as for a rotation by 0.
Think of quaternions as instant rotations - rotating by 4π radians instantly is the same as doing nothing.
This is not what you want when you animate rotation of 4π radians over 20 seconds. You can solve it by creating an Euler Vector (a 3D vector whose direction represents the axis of rotation, same as in quaternion, while its length represents the speed/angle of the rotation), see https://en.wikipedia.org/wiki/Axis%E2%80%93angle_representation. Later, multiply it by time passed and convert it into quaternion or 3D matrix depending on what your graphics wants.
I am currently trying to get the rotation of an object. I am using C++ and Bullet Physics. This is my code:
btScalar x, y, z;
body[0]->getCenterOfMassTransform().getBasis().getEulerZYX(z, y, x);
However, as I rotate the object around clockwise the number I get from the y (y is vertical in Bullet) axis goes from 0 to -90 to 0 to 90 and finally back to 0 for every quarter rotation. It is close but what I need is for it to go all the way from 0 to 360.
Bullet documentation says:
void getEulerZYX (btScalar &yaw, btScalar &pitch, btScalar &roll, unsigned int solution_number=1) const
and
solution_number Which solution of two possible solutions ( 1 or 2) are possible values
this is because euler angles are ambigous. have you tried solution 2?
I had the same problem. I using LibGDX with Bullet engine, so my code sample on Java, but I'm sure, that it will works on C++ too. Here is my solution (for Z axis):
body.getWorldTransform().getRotation(mRotation);
// That gives you an angle in all range but excluding (85, 95) and (-95, 85). For other axis you can try to get Pitch or Yaw.
float roll = mRotation.getRoll();
// That gives you an angle in range [0, 240). Clockwise and counterclockwise directions isn't detected.
float angle = mRotation.getAngleAround(0, 0, 1);
// Usually 0, but on (85, 95) and (-95, 85) becomes 1 and -1.
int gimbalPole = mRotation.getGimbalPole();
// Using roll (pitch/yaw for other axis) if it's defined, and using angle with gimble pole otherwise.
float rotation = (gimbalPole == 0) ? roll : angle * gimbalPole;
Obtained rotation will be in range (-180, 180). It can be easily converted to [0, 360) range:
if (rotation < 0) rotation += 360;
I'm working on a game that involves moving around a planet and have currently got the player moving in one direction using forward and back using:
if( forward)
yAngle = yAngle + 0.005f;
if( backward)
yAngle = yAngle - 0.005f;
if(left)
zAngle = zAngle - 0.005f;
if(right)
zAngle = zAngle + 0.005f;
Then these angles are put into the matrix.
The character rotates perfectly using the Z Rotation and moves forward and backwards orbiting the planet using the Y rotation but will only move in that direction.
How would I calculate the X and Y rotations so the character will move around the planet in the right direction?
The rotation matrix for a rotation around the x-axis of a 3D Cartesian coordinate system is
1 0 0
0 cos(xAngle) sin(xAngle)
0 -sin(xAngle) cos(xAngle)
The matrix for a rotation around the y-axis is
cos(yAngle) 0 -sin(yAngle)
0 1 0
sin(yAngle) 0 cos(yAngle)
The matrix for a rotation around the z-axis is
cos(zAngle) sin(zAngle) 0
-sin(zAngle) cos(zAngle) 0
0 0 1
You can reverse the sense of rotation of any of those matrices by reversing the signs of its sine terms.
There is no unique definition or matrix for a combined rotation about two axes, however, because the result depends on the order in which the rotations are performed. Of particular relevance to your case, it is still different if the rotations are performed in a series of small, alternating steps.
Therefore, rather than tracking overall rotation angles, I suggest keeping a transformation matrix describing the current orientation (so that, for example, the current direction to the ship is M * (1, 0, 0)). Update that on each tick, or otherwise as needed, by multiplying it by incremental rotation matrices of the above forms describing the movement since the last update. This should give you smooth movement consistent with intuitive controls.
I suggest trying something like this:
if (forward) {
xAngle += 0.005f * (cos(zAngle));
yAngle += 0.005f * (sin(zAngle));
}
if (backward) {
xAngle -= 0.005f * (cos(zAngle));
yAngle -= 0.005f * (sin(zAngle));
}
I'm going on a hunch here, but try it out!
I'm having trouble with gluLookAt.
My camera can rotate along the X and Y axis by piping in relative mouse motion events. The problem is the Z axis - I don't know how to calculate it.
So, my camera can look up, down, left and right. But I can't work out how to completely rotate through 360 degrees!
Could anyone help?
EDIT:
So, here's a trivial example of my code so far:
Point3 test(0,0,0);
Matrix4 camera = Camera::getInstance().getCameraM();
if ((event.motion.xrel > 200) || (event.motion.yrel > 200))
{
break;
}
float mx = (event.motion.xrel);
float my = -(event.motion.yrel);
mx /= 20;
my /= 20;
test.setX(test.getX()+mx);
test.setY(test.getY()+my);
Camera::getInstance().lookAt(Point3(0,0,15),test,Vector3(0,-1,0));
Camera::lookAt simply wraps up the glu lookAt function.
gluLookAt (...) produces an orthogonal view-space. You already know one of the two axes when you call it (Y-axis is given as up), the Z-axis is computed given the direction from eye to center and then finally the X-axis is the cross product between Y and Z.
By the way, you cannot completely rotate through 360 degrees. At +/- 90 degrees off of horizontal you run into a singularity, where there are two possible ways to represent the same angle. If you interpolate rotation through that Euler angle, then you can wind up flipping your orientation. Effectively if two of your three axes become parallel (which happens when you look straight up or straight down) then things get really messy with Euler angles.
This is how I calculate my line of sight vector and the up vector.
ly = sin(inclination);
lx = cos(inclination)*sin(azimuth);
lz = cos(inclination)*cos(azimuth);
uy = sin(inclination + M_PI / 2.0);
ux = cos(inclination + M_PI / 2.0)*sin(azimuth + M_PI);
uz = cos(inclination + M_PI / 2.0)*cos(azimuth + M_PI);
inclination is the angle of the line of sight vector from the xz plane and azimuth is the angle in the xz plane.
This works fine till my inclination reaches 225 degrees. At that point there is a discontinuity in the rotation for some reason. (Note By 225 degrees, I mean its past the upside-down point)
Any ideas as to why this is so?
EDIT: Never mind, figured it out. The azimuth does not need a 180 deg. tilt for the up vector.
I think you are talking of a limit angle of 90 degrees (pi). What you get is a normal behavior. When using gluLookAt, you specify an 'up' vector, used to determine the roll of the camera. In the special case where you are looking upside down, the 'up' vector is parallel to the eye direction vector, so it is not possible to determine the roll of the camera (this problem as an infinite number of solutions, so an arbitrary one is chosen by gluLookAt). May be you should compute this 'up' vector using your inclination and azimuth.