Quaternion based camera - opengl

I try to implement an FPS camera based on quaternion math.
I store a rotation quaternion variable called _quat and multiply it by another quaternion when needed. Here's some code:
void Camera::SetOrientation(float rightAngle, float upAngle)//in degrees
{
glm::quat q = glm::angleAxis(glm::radians(-upAngle), glm::vec3(1,0,0));
q*= glm::angleAxis(glm::radians(rightAngle), glm::vec3(0,1,0));
_quat = q;
}
void Camera::OffsetOrientation(float rightAngle, float upAngle)//in degrees
{
glm::quat q = glm::angleAxis(glm::radians(-upAngle), glm::vec3(1,0,0));
q*= glm::angleAxis(glm::radians(rightAngle), glm::vec3(0,1,0));
_quat *= q;
}
The application can request the orientation matrix via GetOrientation, which simply casts the quaternion to a matrix.
glm::mat4 Camera::GetOrientation() const
{
return glm::mat4_cast(_quat);
}
The application changes the orientation in the following way:
int diffX = ...;//some computations based on mouse movement
int diffY = ...;
camera.OffsetOrientation(g_mouseSensitivity * diffX, g_mouseSensitivity * diffY);
This results in bad, mixed rotations around pretty much all the axes. What am I doing wrong?

The problem is the way that you are accumulating rotations. This would be the same whether you use quaternions or matrices. Combining a rotation representing pitch and yaw with another will introduce roll.
By far the easiest way to implement an FPS camera is to simply accumulate changes to the heading and pitch, then convert to a quaterion (or matrix) when you need to. I would change the methods in your camera class to:
void Camera::SetOrientation(float rightAngle, float upAngle)//in degrees
{
_rightAngle = rightAngle;
_upAngle = upAngle;
}
void Camera::OffsetOrientation(float rightAngle, float upAngle)//in degrees
{
_rightAngle += rightAngle;
_upAngle += upAngle;
}
glm::mat4 Camera::GetOrientation() const
{
glm::quat q = glm::angleAxis(glm::radians(-_upAngle), glm::vec3(1,0,0));
q*= glm::angleAxis(glm::radians(_rightAngle), glm::vec3(0,1,0));
return glm::mat4_cast(q);
}

The problem
As already pointed out by GuyRT, the way you do accumulation is not good. In theory, it would work that way. However, floating point math is far from being perfectly precise, and errors accumulate the more operations you do. Composing two quaternion rotations is 28 operations versus a single operation adding a value to an angle (plus, each of the operations in a quaternion multiplication affects the resulting rotation in 3D space in a very non-obvious way).
Also, quaternions used for rotation are rather sensible to being normalized, and rotating them de-normalizes them slightly (rotating them many times de-normalizes them a lot, and rotating them with another, already de-normalized quaternion amplifies the effect).
Reflection
Why do we use quaternions in the first place?
Quaternions are commonly used for the following reasons:
Avoiding the dreaded gimbal lock (although a lot of people don't understand the issue, replacing three angles with three quaternions does not magically remove the fact that one combines three rotations around the unit vectors -- quaternions must be used correctly to avoid this problem)
Efficient combination of many rotations, such as in skinning (28 ops versus 45 ops when using matrices), saving ALU.
Fewer values (and thus fewer degrees of freedom), fewer ops, so less opportunity for undesirable effects compared to using matrices when combining many transformations.
Fewer values to upload, for example when a skinned model has a couple of hundred bones or when drawing ten thousand instances of an object. Smaller vertex streams or uniform blocks.
Quaternions are cool, and people using them are cool.
Neither of these really make a difference for your problem.
Solution
Accumulate the two rotations as angles (normally undesirable, but perfectly acceptable for this case), and create a rotation matrix when you need it. This can be done either by combining two quaternions and converting to a matrix as in GuyRT's answer, or by directly generating the rotation matrix (which is likely more efficient, and all that OpenGL wants to see is that one matrix anyway).
To my knowledge, glm::rotate only does rotate-around-arbitrary-axis. Which you could of course use (but then you'd rather combine two quaternions!). Luckily, the formula for a matrix combining rotations around x, then y, then z is well-known and straightforward, you find it for example in the second paragraph of (3) here.
You do not wish to rotate around z, so cos(gamma) = 1 and sin(gamma) = 0, which greatly simplifies the formula (write it out on a piece of paper).
Using rotation angles is something that will make many people shout at you (often not entirely undeserved).
A cleaner alternative is keeping track of the direction you look at either with a vector pointing from your eye in the direction where you wish to look, or by remembering the point in space that you look at (this is something that combines nicely with physics in a 3rd person game, too). That also needs an "up" vector if you want to allow arbitrary rotations -- since then "up" isn't always the world space "up" -- so you may need two vectors. This is much nicer and more flexible, but also more complex.
For what is desired in your example, a FPS where your only options are to look left-right and up-down, I find rotation angles -- for the camera only -- entirely acceptable.

I haven't used GLM, so maybe you won't like this answer. However, performing quaternion rotation is not bad.
Let's say your camera has an initial saved orientation 'vecOriginalDirection' (a normalized vec3). Let's say you want it to follow another 'vecDirection' (also normalized). This way we can adapt a Trackball-like approach, and treat vecDirection as a deflection from whatever is the default focus of the camera.
The usually preferred way to do quaternion rotation in the real world is using NLERP. Let's see if I can remember: in pseudocode (assuming floating-point) I think it's this:
quat = normalize([ cross(vecDirection, vecOriginalDirection),
1. + dot(vecDirection, vecOriginalDirection)]);
(Don't forget the '1. +'; I forget why it's there, but it made sense at one time. I think I pulled my hair out for a few days until finding it. It's basically the unit quaternion, IIRC, which is getting averaged in, thereby making the double-angle act like the angle... maybe :))
Renormalizing, shown above as 'normalize()', is essential (it's the 'N' in NLERP). Of course, normalizing quat (x,y,z,w) is just:
quat /= sqrt(x*x+y*y+z*z+w*w);
Then, if you want to use your own function to make a 3x3 orientation matrix from quat:
xx=2.*x*x,
yy=2.*y*y,
zz=2.*z*z,
xy=2.*x*y,
xz=2.*x*z,
yz=2.*y*z,
wx=2.*w*x,
wy=2.*w*y,
wz=2.*w*z;
m[0]=1.-(yy+zz);
m[1]=xy+wz;
m[2]=xz-wy;
m[3]=xy-wz;
m[4]=1.-(xx+zz);
m[5]=yz+wx;
m[6]=xz+wy;
m[7]=yz-wx;
m[8]=1.-(xx+yy);
To actually implement a trackball, you'll need to calculate vecDirection when the finger is held down, and save it off to vecOriginalDirection when it is first pressed down (assuming touch interface).
You'll also probably want to calculate these values based on a piecewise half-sphere/hyperboloid function, if you aren't already. I think #minorlogic was trying to save some tinkering, since it sounds like you might be able to just use a drop-in virtual trackball.

The up angle rotation should be pre multiplied, post multiplying will rotate the world around the origin through (1,0,0), pre-multiplying will rotate the camera.
glm::quat q_up = glm::angleAxis(glm::radians(-upAngle), glm::vec3(1,0,0));
q_right = glm::angleAxis(glm::radians(rightAngle), glm::vec3(0,1,0));
_quat *= q_right;
_quat = q_up * _quat;

Related

How to store and modify angles in 3D space

This isn't about understanding angular physics, but more how to actually implement it.
For example, I'm using a simple linear interpolation per frame (with dT)
I'm having trouble with the angular units, I want to be able to rotate around arbitrary axes.
(with glm)
Using a vec3 for torque, inertia and angular velocity works excellent for a single axis.
Any more and you get gimbal lock. (i.e. You can rotate around a local x, y or z but superimposing prevents proper results)
Using quaternions I can't get it to function nicely with time, inertia or for an extended period.
Is there any tried-and-true method for representing these features?
The usual solution is to use the matrix representation of rotation. Two rotations in sequence can be expressed by multiplying their respective matrices. And because matrix multiplication is not symmetric, the order of the 2 rotations matters - as it should.

OpenGL rotation vector from matrix

Ok, so here is what I have:
an abstract "Object" class which I made in a framework to use it as a base class for all 3D objects.
a Matrix4 member of this class which has the sole purpose of storing rotation info for the object.
some functions that multiply the matrix: for each of the yaw, pitch & roll rotations (both global and local), I made a method that multiplies the above rotation matrix with a new matrix.
e.g.: if you locally yaw the object by 45 degrees in CCW direction, then
rotMatrix = newRotationZMatrix(45) * rotMatrix;
What I would like to know is what is the best way of getting the global rotation of the object as a vector - generally speaking, how do you get the rotation angles around X,Y and Z from a transformation matrix that contains JUST rotations.
There are techniques to obtain that, just get the euler angles from a rotation matrix, it involves a bit of math. Here you can read about it.
I have a similar class.
It's one of the most fun part to me work with matrix 3D.
Well, let's to the answer.
I'm working with OpenGL ES, so to me performance is a crucial part. I make a routine, test it, test it, stress tests, rebuild whole routine and test, test... until to find the best way to save memory and increase performance.
So, thinking in maximum performance, and if you, like me, have a class to work with matrices, I tell you that the best way to get it is:
• Store the rotation's values in variables before put it in the matrix.
At this way you don't need to perform complex Euler calculations at every time that you want to get these values. The same is true for the scale's values. Just the translations that have separated indexes don't need this.

opengl matrix rotation quaternions

Im trying to do a simple rotation of a cube about the x and y axis:
I want to always rotate the cube over the x axis by an amount x
and rotate the cube over the yaxis by an amount y independent of the x axis rotation
first i naively did :
glRotatef(x,1,0,0);
glRotatef(y,0,1,0);
then
but that first rotates over x then rotates over y
i want to rotate over the y independently of the x access.
I started looking into quaternions, so i tried :
Quaternion Rotation1;
Rotation1.createFromAxisAngle(0,1, 0, globalRotateY);
Rotation1.normalize();
Quaternion Rotation2;
Rotation2.createFromAxisAngle(1,0, 0, globalRotateX);
Rotation2.normalize();
GLfloat Matrix[16];
Quaternion q=Rotation2 * Rotation1;
q.createMatrix(Matrix);
glMultMatrixf(Matrix);
that just does almost exactly what was accomplished doing 2 consecutive glRotates ...so i think im missing a step or 2.
is quaternions the way to go or should i be using something different? AND if quaternions are the way to go what steps can i add to make the cube rotate independently of each axis.
i think someone else has the same issue:
Rotating OpenGL scene in 2 axes
I got this to work correctly using quaternions: Im sure there are other ways, but afeter some reseatch , this worked perfectly for me. I posted a similar version on another forum. http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=280859&#Post280859
first create the quaternion representation of the angles of change x/y
then each frame multiply the changing angles quaternions to an accumulating quaternion , then finally convert that quaternion to matrix form to multiply the current matrix. Here is the main code of the loop:
Quaternion3D Rotation1=Quaternion3DMakeWithAxisAndAngle(Vector3DMake(-1.0f,0,0), DEGREES_TO_RADIANS(globalRotateX));
Quaternion3DNormalize(&Rotation1);
Quaternion3D Rotation2=Quaternion3DMakeWithAxisAndAngle(Vector3DMake(0.0f,-1.0f,0), DEGREES_TO_RADIANS(globalRotateY));
Quaternion3DNormalize(&Rotation2);
Matrix3D Mat;
Matrix3DSetIdentity(Mat);
Quaternion3DMultiply(&QAccum, &Rotation1);
Quaternion3DMultiply(&QAccum, &Rotation2);
Matrix3DSetUsingQuaternion3D(Mat, QAccum);
globalRotateX=0;
globalRotateY=0;
glMultMatrixf(Mat);
then draw cube
It would help a lot if you could give a more detailed explanation of what you are trying to do and how the results you are getting differ from the results you want. But in general using Euler angles for rotation has some problems, as combining rotations can result in unintuitive behavior (and in the worst case losing a degree of freedom.)
Quaternion slerp might be the way to go for you if you can find a single axis and a single angle that represent the rotation you want. But doing successive rotations around the X and Y axis using quaternions won't help you avoid the problems inherent in composing Euler rotations.
The post you link to seems to involve another problem though. The poster seems to have been translating his object and then doing his rotations, when he should have been rotating first and then translating.
It is not clear what you want to achieve. Perhaps you should think about some points and where you want them to rotate to -- e.g. vertex (1,1,1) should map to (0,1,0). Then, from that information, you can calculate the required rotation.
Quaternions are generally used to interpolate between two rotational 'positions'. So step one is identifying your start and end 'positions', which you don't have yet. Once you have that, you use quaternions to interpolate. It doesn't sound like you have any time-varying aspect here.
Your problem is not the gimbal lock. And effectively, there is no reason why your quaternion version would work better than your matrix (glRotate) version because the quaternions you are using are mathematically identical to your rotation matrices.
If what you want is a mouse control, you probably want to check out arcballs.

3d geometry: how to align an object to a vector

i have an object in 3d space that i want to align according to a vector.
i already got the Y-rotation out by doing an atan2 on the x and z component of the vector. but i would also like to have an X-rotation to make the object look downwards or upwards.
imagine a plane that does it's pitch yaw roll, just without the roll.
i am using openGL to set the rotations so i will need an Y-angle and an X-angle.
I would not use Euler angles, but rather a Euler axis/angle. For that matter, this is what Opengl glRotate uses as input.
If all you want is to map a vector to another vector, there are an infinite number of rotations to do that. For the shortest one, (the one with the smallest angle of rotation), you can use the vector found by the cross product of your from and to unit vectors.
axis = from X to
from there, the angle of rotation can be found from from.to = cos(theta) (assuming unit vectors)
theta = arccos(from.to)
glRotate(axis, theta) will then transform from to to.
But as I said, this is only one of many rotations that can do the job. You need a full referencial to define better how you want the transform done.
You should use some form of quaternion interpolation (Spherical Linear Interpolation) to animate your object going from its current orientation to this new orientation.
If you store the orientations using Quaternions (vector space math), then you can get the shortest path between two orientations very easily. For a great article, please read Understanding Slerp, Then Not Using It.
If you use Euler angles, you will be subject to gimbal lock and some really weird edge cases.
Actually...take a look at this article. It describes Euler Angles which I believe is what you want here.

How do I extract the angle of rotation from a QTransform?

I have a QTransform object and would like to know the angle in degrees that the object is rotated by, however there is no clear example of how to do this:
http://doc.trolltech.com/4.4/qtransform.html#basic-matrix-operations
Setting it is easy, getting it back out again is hard.
Assuming, that the transform ONLY contains a rotation it's easy: Just take the acos of the m11 element.
It still works if the transform contains a translation, but if it contains shearing or scaling you're out of luck. These can be reconstructed by decomposing the matrix into a shear, scale and rotate matrix, but the results you get aren't most likely what you're looking for.
The simplest general way is to transform (0,0) and (1,0), then use trigonometric functions (arctan) to get the angle
The Transformation Matrix is an implementation used for 3d graphics. It simplifies the math in order to speed up 3d positional / rotational orientations of points / objects. It is indeed very hard to pull out orientation from the Transformation because of the way it accumulates successive translations / rotations / scales.
Here's a suggestion. Take a vector that points in a simple direction like (1,0,0), and then apply the Transform to it. Your resulting vector will be translated and rotated to give you something like this: (27.8, 19.2, 77.4). Apply the Transform to (0,0,0), to get something like (26.1, 19.4, 50.8). You can use these two points to calculate the rotations that have been applied based on knowing their starting points of (1,0,0).
Does this help?
Generally you need an inverse trig function, but you need to watch out for quadrant ambiguities, and this is what you should use atan2 (sometimes spelled arctan2). So either rotate a unit vector [0, 1] to [x, y] and then use atan2(y,x), or if the matrix is only implementing a rotation you can use atan2(m12,m11). (These are similar to Javier and Nils answers, except they don't use atan2.)
I was using QGraphicsItem with just setRotate and wasn't having any kind of problem, until I add a rotate group functionality. The problem is that when destroyItemGroup is called, it applies the rotation as a transformation to the items, and not as a rotation. Because of that I had to recover the rotation from this QTransform object.
My fix was to add the following lines to the itemChange method (credit to tom10's answer):
QVariant MyGraphicItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
if(change == ItemTransformChange)
{
auto transform = value.value<QTransform>();
setRotation(rotation() + qRadiansToDegrees(qAtan2(transform.m12(), transform.m11())));
return QVariant();
}
...
}
PS.: The other solution with acos and m11() didn't work. It crashes for certain values, as explained by tom10.