Advertisement

Help with Quaternion rotation (6DOF)

Started by February 28, 2016 04:56 PM
46 comments, last by SBD 8 years, 5 months ago

The order of multiplication is very important. If your ship is facing towards (0, 0, 1) and you think of (0, 1, 0) as "up", you should probably apply roll first (rotation around (0, 0, 1)), then pitch (rotation around (1, 0, 0)) and finally yaw (rotation around (0, 0, 1)).

Arr i was going to say the "fix i posted" was still getting random roll (z axes) when moving in both the x and y axes. I wonder it was due to that, i shall try to fix this (with both local and global axes for understanding). Thanks for everyones help so far :)


I assume "MATs" means "matrices", since the rest of your post makes sense with that interpretation.

correct! <g>


However, matrices are not the same thing as "eulers", assuming by that you mean "Euler angles"; they are not even close.

quite true. this has been a topic of interest to me for so many years that i tend to get sloppy with my terminology.

by that i mean that a set of global euler angle rotations applied in a specific order results in a rotation matrix. so the eulers andr the mat are simply different representations of the same set of rotations (or orientation as i like to call it).


Whether he uses matrices or quaternions is completely irrelevant

for this type of application, the major differences appear to be:

1. using mats, you can basically read off the forward, up, and right vectors of the ship from the mat (forget which rows / columns). this can be handy, as your local rotations are pretty much always around these axes.

2. quats degrade about half as fast, so re-ortho-normalization should only be required half as often.

3. odds are it all gets converted to a mat for drawing by the graphics library/engine anyway.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Advertisement


The easiest way to do this is to track an up, right and forward vector per ship.

this is what i mean by using a mat (using an orientation matrix).

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

lexington, let me try to be more precise.

you can store the ships orientation as mat or quat or 3 vectors (forward, up, and right). mats seems to be easiest. i've done at least half a dozen of these games, and i always hum and haw over which way to do it, and i always end up choosing mats, and i've never been sorry (not yet at least). try pseudo-coding all three to see which is least complex for your situation.

use axis-angle to do local rotations around your vectors, if you use a mat you can read off the vectors from it. you can also calculate the vectors from a quat. you can use rotate point (vector) by mat, or rotate point (vector) by quat on your three unit x,y,z vectors to convert a mat or quat to forward, up, and right vectors. but for mats you can also just read of the vectors from specific rows and columns (don't recall which ones off hand), which is even easier.

the rotations are not commutative, so must be done in in the same order every time (and undone in the reverse order every time to un-rotate).

after applying a number of rotations, your vectors will begin to skew due to floating point error. so right no longer points exactly right in relation to the forward vector, and thus pitching no longer goes exactly up and down, same for the up vector with respect to the forward vector, up skews, and you no longer turn exactly left and right.

to fix this, you must occasionally make the three vectors mutually perpendicular (orthogonal) and of unit length again. this is called re-ortho-normaliztion and is done with Gauss' algo:

old_forward cross old_right = new_up

old_forward cross new_up = new_right

new_forward = old forward

doing it in this order preserves the forward vector, hopefully preserves the up vector, and does most of the fixup on the right vector.

at that point, you can renormalize the vectors, and you're good to go, ready for the next set of rotations. use the vectors to create your new orientation matrix or quat if you store orientation that way.

im a perfectionist, so i typically re-ortho-normalize after every update, so i apply local xrot (pitch), yrot (yaw/turn), zrot(roll) - always in that order - then re-ortho-normalize.

but you only have to do it "often enough" to keep things from skewing more that what you consider acceptable. so you may only need to do it every 10 updates or 100 updates. as i recall, they tend to skew rather quickly, becoming worthless withing a few thousand or tens of thousands of updates. IE with a few minutes of gameplay, depending on your update rate and how often you change orientation while playing.

when it comes time to draw, use your vectors to create an orientation mat, append it to your scale mat, then append your translation mat, and send it off to the graphics engine.

if you store orientation as a mat, simply concatenate it onto your scale mat, then cat on your translation matrix and call draw.

if you store your orientation as a quat, convert it to a mat, cat it to your scale mat, then cat on your translation and call draw.

your graphics library may offer other draw calls that take quats instead of mats which you can use instead. internally, they will most likely convert those quats to mats and then call the mat version of draw.

abbreviations used:

quat = quaternion

mat = matrix

vectors = local forward, up, and right unit vectors

cat = concatenate

append = concatenate

cross = the cross product

axis-angle = rotation about an arbitrary axis formula (IE the local rotation formula)

hint: use your left thumb, pointer, and middle fingers to represent up, forward and right respectively, and use that to visualize your rotations. same idea as the airplane model. note that this can lead to you doing a lot of weird hand gestures that others might find a bit odd <g>.

but once you start coding this sucker, you'll find yourself doing it all the fricking time.

and it really does help.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

OK two things to say

1. thanks Alvaro for helping me understand 3d rotation better and allowing me to have fixed this problem :)

2. swiftcode - i tried doing what you suggested with the local angles but it produced very weird results, i could rotate in one direction but if i then wanted to rotate in any other direction it would jitter. the current code i have for this is

the rotations


if(currentmouse.x < 540)
     {
       rotation.y = -(540 - currentmouse.x) * 0.002f * deltaTs;
     }
     else if(currentmouse.x > 740)
     {
       rotation.y = -(540 - currentmouse.x) * 0.002f * deltaTs;
     }
     else
     {
       rotation.y = 0.0f;
     }

     if(currentmouse.y > 400)
     {
        rotation.x = (currentmouse.y - 400) * 0.005f * deltaTs;
     }
     else if(currentmouse.y < 320)
     {
       rotation.x = -(320 - currentmouse.y) * 0.005f * deltaTs;
     }
     else
     {
       rotation.x = 0.0f;
     }

the axis angles equations and applying the vector


  right = currentrotation * right;
  up = currentrotation * up;
  forward = currentrotation * forward;

  glm::quat xrotation = glm::angleAxis((rotation.x),right);
  glm::quat yrotation = glm::angleAxis((rotation.y),up);
  glm::quat zrotation = glm::angleAxis((rotation.z),forward);

  currentrotation = currentrotation * yrotation * xrotation * zrotation;

@Norman barrow

Just to ask a couple of questions, with the forward, right and up, are you changing these by the ships rotation at all or are you just keeping them at up = (0,1,0) forward = (0,0,1) and right = (1,0,0), im guessing its the former and if it is can you at all help address the problem I am getting in comment 25, if it is just the "re-ortho-normaliztion" then I can do that and test it. Also just to clarify something else, is it better to store every rotation that has happened with the angle vector (in my example its vec3 rotation) and then reset the objects rotation to default and then recalculate the rotation or is it ok to just keep adding rotations as i am doing in the example above.

Thanks for the help :)

Advertisement

assuming a left hand coordinate system:

when the game starts, the ship is at rotation angles 0,0,0, so forward points in the direction of the positive z axis, right points in the direction of the positive x axis, and up points in the direction of positive y axis.

now we apply a turn. we'll hit left arrow once, which turns us 5 degrees left. so we want to do an axis-angle rotation around the up vector. so we send our up vector as the axis, -5 degrees as the amount (remember we're turning left, and right is positive), and the end of the forward vector as the point to rotate. call the axis-angle (rotate point about arbitrary axis) routine using those parameters. this gives us a new point, whichis the end of our new forward vector. now do the same for the right vector: up is the axis, -5 degrees is the angle, and the end of the right vector is the point. this gives us our new right vector. since we're rotating around the up vector, it doesn't move, so it remains unchanged. the result is you've applied a discrete finite rotation of -5 degrees around the local y axis, and you now have your new forward, up and right vectors, representing the new orientation of the ship. so far so good. only problem is, due to floating point error, the vectors are not quite orthogonal any more, and they are no longer exactly of unit length. not enough to make a big difference yet, but the error accumulates over time, and things get whacked beyond use within minutes. so sooner or later you're going to have to re-ortho-normalize.

now its time to draw. stuff your vectors into a mat (put the components in the correct rows/columns). this will be your rotation matrix. create your scale matrix, cat on your rotation matrix, cat on your translation, and draw.

ok, time to turn again. we have a ship which is currently pointing 5 degrees left of looking down the z axis. its orientation is stored in our vectors, and also happens to be in the rotation matrix we used for drawing. don't really need it in both places, this is why i use mats. i just keep the rotation matrix around all the time and change the vectors in it as needed, so i'm storing the vectors in the matrix. lookup which rows/columns of a matrix handle rotation - all this will make much more sense.

so we're turning.... this time we pitch up 10 degrees and roll left 10 degrees. our rotation order is always xr, yr, zr, (or whatever you choose) so we pitch (xr), then roll (zr).

so call axis-angle: axis=right, angle=-10 (pitch down is positive), point = end of the forward vector. this gives us our new forward vector.

and axis=right, angle=-10 (pitch down is positive), point = end of the up vector. this gives us our new up vector.

now roll:

call axis-angle again: axis=new forward (NOT old forward, we've already pitched!), angle = 10 degrees (positive roll is counter clockwise or left - remember your left hand rule). point = end of the right vector. this gives us our new right vector.

and axis=new forward, angle=10, point = end of NEW up (it changed when we pitched) gives us our "new new" up <g>.

again, float errors will occur requiring periodic re-ortho-normalization.

and now the general case:

roll by amount zr, pitch by amount xr, and yaw by amount yr:

assume our rotation order is xr,yr,zr.

so we pitch first:

axis = right, angle = xr, point = end of forward vector => new forward vector

axis = right, angle = xr, point = end of up vector => new up vector

then yaw:

axis=new up, angle = yr, point = end of new forward vector => new new forward vector

axis=new up, angle = yr, point = end of right vector => new right vector

now roll:

axis=new new forward, angle = zr, point = end of new right vector => new new right vector

axis=new new forward, angle = zr, point = end of new up vector => new new up vector

so your final orientation is given by the new new forward vector, the new new right vector, and the new new up vector.

but again, you're skewed and not unit length. so now we re-ortho-normalize. do the cross product algo i described, followed by normalizing the vectors.

note that the orientation is stored in the vectors, and they always point in the correct directions in relation to the ship, and are not recalculated every time.

it IS possible to do that though. once you have your new vectors after rotating the ship, you can used the forward vector to solve for the equivalent xr and yr euler angles (global rotations). roll is a bit trickier. i don't do this myself, so i'm not 100% on this, but once you have the global xr and yr eulers, you un-rotate ,up around the global y axis by the yr euler angle, , then un-rotate it by the xr euler angle. remember, you're un-rotating around global axes here, so your order is reversed, zr, then yr, then xr, and all your rotations are negated: -10 instead of 10, for example, and you use the global rotation formulas. once you do that roll should be the angle between the y axis and the projection of the up vector onto the xy plane. this gives you three global euler angles equivalent to the orientation of the vectors. from there you rebuild the matrices and vectors again next frame. all in all, not a trivial amount of work. now you see why i (and most folks) don't do it.

whenever you concatenate successive small rotations to an orientation matrix/quat/vectors, skewing occurs due to float error. double doesn't help - just delays the problem. converting to eulers doesn't really help since you're using skewed vectors to calculate the eulers. so re-ortho-normalization is unavoidable, and will very soon become a very good friend of yours <g>.

quick answer to you previous post: store in vectors, just keep rotating them, re-othero-normalize from time to time. not sure what you're doing in your code, but it doesn't look like it does the same thing as the algos i'm talking about. the way you create your rotation matrix looks especially suspicious. compare your code to the above algo and you should soon find where you'e going astray.

and soon you too will have "the amazing flying axis brothers" (up, right , and forward vectors as placeholder graphics for testing local rotations) swooping, diving, and barrel rolling across your screen. <g>

so, now hopefully all is clear, and in two weeks we get to see your demo of pixel perfect local rotations, right? <g> (just kidding).

actually, odds are it won't even take you that long to get it working, a few days maybe.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

@Norman barrow I'm believe i understand most of what you said but there is a couple of things I want to make sure off if its ok i wanna try and psudocode what I believe you saying the process is and if you see something wrong, if you could clear it up it would be nice. I will also be trying to use quaternions as I started with using them so it would be nice to finish this with an understanding of them first.

So currently for an object I will have a quaternion to represent its orientation (which I will turn into a mat4x4 that I will then times with the translate matrix (im not wanting to scale anything ATM))

e.g.


  glm::mat4 RotationMatrix = glm::toMat4(currentrotation);
  glm::mat4 translationmatrix = glm::translate(glm::mat4(1.0f), position);
  modelMatrix = translationmatrix * RotationMatrix;

then I also have 3 vectors

forward = glm::vec3(0,0,1);

up = glm::vec3(0,1,0);
right = glm::vec3(1,0,0);
so lets take the example we want to rotate up 6 degrees (so 6)
so this is where I'm want to confirm I understand what you said. With the fact that I am using quaternions im guessing i would first do is create a quat with that rotation
quatx = axisangle(6,right)
so from

axis = right, angle = xr, point = end of forward vector => new forward vector

axis = right, angle = xr, point = end of up vector => new up vector

im guessing i would then do

up = quatx * up

forward= quatx * forward

then for the next part i would do the yaw ofcs we would not have any rotation for this case but if we had a rotation of 6 up and 6 right we would do
quaty = axisangle(6,up)

axis=new up, angle = yr, point = end of new forward vector => new new forward vector

axis=new up, angle = yr, point = end of right vector => new right vector

forward= quaty * forward
right = quaty * right
then lastly for roll
quatz = axisangle(6,forward)

axis=new new forward, angle = zr, point = end of new right vector => new new right vector

axis=new new forward, angle = zr, point = end of new up vector => new new up vector

up = up * forward
right = quaty * right
after this i would want to do the following

old_forward cross old_right = new_up

old_forward cross new_up = new_right

new_forward = old forward

then normalize all 3 vectors

so this is the other point I am still a bit confused on, from these 3 new vectors do I construct a matrix/quat and then that is the rotation matrix or i create the rotation quat from the 3 quats like

or currentrotation = currentrotation * quaty * quatx * quatz;

so lets take the example we want to rotate up 6 degrees (so 6)

up is -6 degrees

quatx = axisangle(6,right)

forward= quatx * forward

if these two function calls in the library you're using rotate the forward vector around the right vector by 6 degrees (-6 actually), then yes, you got it.

then normalize all 3 vectors

all good up to here. the big thing to remember is that you have to pass the vectors you get out of each rotation into the next rotation, and rotations always have to be done in the same order, and undone in the same reverse order.

from these 3 new vectors do I construct a matrix/quat and then that is the rotation matrix

yes

or i create the rotation quat from the 3 quats like currentrotation = currentrotation * quaty * quatx * quatz;

that might also work. if you have the current orientation in a quat, then theoretically, assuming a rotation order of xr,yr,zr, quatz o quaty o quatx o quat_current = new quat_current, where "o" is the composition operator. i don't use quats, so i'd recommend you try it both ways to confirm the results are the same. note again that the order matters, and composition of quaternion rotations are applied from last to first. so they will appear in your composition statement in the reverse order the rotations are made, and current_quat (your starting orientation) will always be the last one in the list for composition.

IE (assuming xr,yr,zr order):

new_quat_current = quatz o quaty o quatx o quat_current

= NOT =

new_quat_current = quat_current o quatx o quaty o quatz

or some other order than the rotations in reverse order, followed by the current orientation.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

up is -6 degrees

Wait I thought up was positive and down was negative and right was positive and left was negative

Yer ill try it both ways and see

also as a added cheeky question, is it better to rotate the ship and base the camera off of it or is it better to rotate the camera and base the ship off of that

This topic is closed to new replies.

Advertisement