quaternions

Posted By: JibbSmart

quaternions - 02/27/07 10:55

g'day guys!

i saw someone mention quaternions somewhere on the a6 forums, and i was getting really frustrated with some rotational stuff in my project (bloody Euler angles ) so i went on wikipedia and some other online resources and taught myself the theory behind quaternions, which is ridiculously easy if u know a little about complex numbers, and possibly even if you don't.
anyway, here is the fruit of my research! i decided anyone who wants to use quaternions would have figured it out one way or another, and considering most of these functions almost directly quote online sources i have no reason to keep this to myself. enjoy:
quaternions.c

a brief description for the contents:
QUATERNION
a quaternion (struct) containing w, x, y and z floats. there is no globally used convention but in this case "w" is the scalar part and "x,y,z" the axis.

quat_multiply(QUATERNION* end_result,QUATERNION* a,QUATERNION* b)
sets the quaternion "end_result" to the product of quaternions "a" and "b" (in that order -- remember quaternions are not commutative). this is effectively rotating "b" by "a", around the axis represented in "a". this is the main reason i am using quaternions.

quat_for_ang(QUATERNION* q,ANGLE* a)
meant to set the change the contents of "q" to represent the euler angle "a". untested, so please let me know if it doesn't work and point me in the right direction if you know what should be done. -- actually, doesn't work. everything else is tested quite reasonably i think, so they should work. i'll get back to you when i've had a look at this but i haven't found a need for quat_for_ang yet.

quaternize(var a,VECTOR* v,QUATERNION* q)
i don't know if quaternize is a real word, but this will set the contents of quaternion "q" to represent the rotation "a" degrees about the axis "v".

quat_to_unit(QUATERNION* q)
gives "q" a magnitude of "1". for a multiplication to accurately represent a quaternion rotation each quaternion needs a magnitude of 1. this does that for you.

quat_set(QUATERNION* q,QUATERNION* u)
sets quaternion "q" to equal "u".

just put #include "quaternions.c" after your default and acknex includes, and then these functions and quaternions should work beautifully

i hope at least a few of you get some use out of these they're good for rotating around any axis you want. if you find and confirm any errors, please notify me either by pm or in this thread!
i haven't been able to test it for super long but it seems to be working really well from what i can see!

back to my design & technology project.

julz
Posted By: A.Russell

Re: quaternions - 02/28/07 03:46

This is really good. I thought GS used Euler angles, so could you show a code snippet of how you would use this?
Posted By: JibbSmart

Re: quaternions - 02/28/07 05:04

thank you very much

these are part of what i used to test it (comments added so you know what i'm doing):
Code:

VECTOR camera_axis;

function cam_control()
{
temp.x = - 300; // put the camera 300 quants behind the ball, based on camera's orientation
temp.y = 0;
temp.z = 0;
vec_rotate(temp,camera.pan);
vec_add(temp,my.x);
vec_lerp(camera.x,camera.x,temp,0.5);
camera.pan -= mouse_force.x * 5; // mouse controls pan and tilt of camera
camera.tilt -= mouse_force.y * 5;
temp.x = 0; // this sets the global VECTOR "camera_axis", which is
temp.y = 1; // relative to the camera and used to test quaternion
temp.z = 0; // rotations. play with these values for different axes :P
vec_rotate(temp,camera.pan);
vec_set(camera_axis,temp);
}

action ball()
{
wait(1);
c_setminmax(me);
vec_set(camera_axis,nullvector); // sorts out initialization problems
QUATERNION my_rot; // this quaternion contains my orientation
QUATERNION my_spin;// and this contains how i want to spin the ball
ANGLE my_angle; // euler orientation
vec_set(my_angle,my.pan);
my_rot.w = 1; // this is like a default orientation, as quat_for_ang doesn't work yet
my_rot.x = 0;
my_rot.y = 0;
my_rot.z = 0;
var js = 0; // you'll see what this is for...
while(1)
{
js += time_frame*50/16; // basically this loop will run 50 times a second, which
while(js>0) // isn't needed for quaternions at all, but just something i do.
{
quaternize((key_w*2),camera_axis,my_spin); //create a quaternion that will rotate around the camera-controlled axis, and spin if "w" is pressed
quat_to_unit(my_spin); // normalize both
quat_to_unit(my_rot);
quat_multiply(my_rot,my_spin,my_rot); // rotate
ang_for_quat(my_angle,my_rot); // and convert to euler for GS use
vec_sub(my_angle,my.pan); // just finds the rotation needed for c_rotate
c_rotate(me,my_angle,IGNORE_PASSENTS|IGNORE_PASSABLE);
cam_control(); // control the camera
js -= 1; // don't want an infinite loop!
}
wait(1);
}
}

GS does use Euler angles, and in fact most sources use a different Euler rotation order, so it wasn't easy finding what i needed it still ends up using a Euler angle, but the quaternion representation is unaffected by the Euler translation, and is MUCH easier to use when rotating around an arbitrary axis. i plan on using these for my car game i'm making, as Euler angles were proving to be really annoying and often unrealistic.

julz

EDIT: i realized that x- and y- seem to be inverted when using quaternize, which doesn't seem to be fixing as easily as i thought i am certain the euler representations of the quaternions are correct, tho. when rotating around a vector using quaternize, i think the x and y values need to each be multiplied by -1 first. i'm pretty sure this won't be too much of a problem.

EDIT 2: above edit no longer applicable!
Posted By: ventilator

Re: quaternions - 02/28/07 09:25

Quote:

[...] which is ridiculously easy if u know a little about complex numbers, and possibly even if you don't.


really? damn, i didn't get them when i looked into them a while ago. maybe i should (re)learn complex numbers first.

interesting contribution but what can be done with it that can't be done with the built-in quaternion functions ang_add/ang_rotate?

maybe you could add a spherical interpolation function?
Posted By: JibbSmart

Re: quaternions - 02/28/07 10:07

lol the ridiculous ease of quaternions was really the basic theory behind it... not the theory behind the derivations. that really got me. at first i was using ang_add and ang_rotate, and they weren't working so well for me. the trouble with them, is they only rotate around the new axis. these quaternions allow you to define your own arbitrary axis. that's y i did all this.

haha!! yes!!! i've just figured out i made a blissfully stupid mistake! the basic explanation is that the x and y values are NOT inverted for quaternize. i just made a couple of misinterpretations of my own

julz

EDIT: example for quaternion advantages: wheels in a car which is accelerating rotate forward at varying rates, depending on speed (doable with quaternions OR ang_add) but also rotate left and right about an axis which is in the same direction as "up" in relation to the car uses these wheels. quaternion rotations make this easy, but it is impossible (or extremely difficult) with ang_add or ang_rotate if the wheels don't have the same tilt as the car. i'm sure u know what i mean
Posted By: Tor

Re: quaternions - 02/28/07 23:51

Quaternions are really awesome. They really aren't that more complicated and with the right functions and a math book you can be up and running in almost no time. There is an excellent example of quaternion rotation on www.ultimategameprogramming.com under the OpenGL examples/tutorials.

Definately saved my butt a few times in classes already. So much easier to rotate things with Quat's than with euelers or any other method I've seen so far. Especially if you have to take into account matrixies.

Cheers!
Posted By: JibbSmart

Re: quaternions - 03/01/07 01:45

yeh im slightly embarrassed by the amount of time it took me to set these up, but i have no maths books that cover anything outside of high-school and almost every online source i found was either converting quaternions to matrices or was using a different Euler order than GS. wikipedia had the best information i could find, but it wasn't working so i tried other stuff. in the end, wikipedia had the most accurate info, except i needed to change a positive value to a negative, and almost everything was solved.

another issue i had was the lack of an atan2 function in GS, but i just wrote a basic replacement (which you will find in quaternions.c). jcl said a proper atan2 function will be implemented in a future update, so in that case mine will probably have to be commented out to prevent crashes in that update.

julz

EDIT: 300 posts! senior member now
Posted By: Captain_Kiyaku

Re: quaternions - 03/01/07 11:01

*shaking* quaternions... my nightmare came true @_@ nooo *runs away*

i'm SO glad someone implemented them into 3dgs, cause i will never touch them again D:

Thanks a lot for the script, i will test it out soon!
Posted By: JibbSmart

Re: quaternions - 03/02/07 22:36

haha, i'm glad some people appreciate it. i know it has very few functions, and one of them doesn't work, but i will expand on it soon (maybe some slerp, definitely fix quat_for_angle, and some vector functions). i can only begin doing so, however, after thursday, as i have left a pretty big school assessment task too late so that's all i'll be doing until then (besides a glance at the forums on occasion )

julz
Posted By: JibbSmart

Re: quaternions - 03/13/07 09:42

okay! a decent sized update:

quat_add, quat_sub, quat_conjugate, and vec_rot_by_quat were all added (vec_rot_by_quat is particularly useful), and quat_for_ang was fixed, so it is fully functional. it doesn't have slerp, but maybe i'll add that in the future if i can find a proper formula for quaternions to the power of something <1 and >0. i don't need slerp so don't count on it i'm busy enough as it is.

it is a new link, because now it is zipped with a readme explaining the functions.

get it here: quaternions.zip (2k)

let me know if anything does something it isn't supposed to. i hope some of you get some use out of these!

julz
Posted By: JibbSmart

Re: quaternions - 03/14/07 09:39

little update: vec_for_quat creates a unity vector in the direction of the quaternion. <-- not the direction of the quaternion's vector, but the direction that the rotation points

please reply if you do or already have downloaded it, i'd like to know if these are any use to anyone. regardless, if i update the script, i'll upload it and let you know.

the link is in my last post ^^

julz
Posted By: quantum69

Re: quaternions - 03/29/07 03:37

Was looking at your lib and noticed you could get better performance by doing the <x>/2 ahead of time, store it in a local var and use it instead of performing all the divides. Not that todays FPU's aren't fast, but every little bit helps.

Just a thought.

Quantum Mechanic
Better living at the subatomic level.
Posted By: JibbSmart

Re: quaternions - 03/29/07 10:31

thanks for the tip but local variables slow down function calls too. i don't know which would be slower, but it isnt the end of the world i've uploaded it again. quat_to_unit has been fixed up so that the value of the angle is preserved. this wasn't a problem when the magnitude was only slightly off, but i was having problems when quaternizing long vectors.

julz
Posted By: Ambassador

Re: quaternions - 03/30/07 23:03

Quote:

Quote:

[...] which is ridiculously easy if u know a little about complex numbers, and possibly even if you don't.


really? damn, i didn't get them when i looked into them a while ago. maybe i should (re)learn complex numbers first.




Actually I don't know how quaternion inner systems work but I'm still using them in my engine . Just looked into some examples and tutorials/stuff and coded my own quaternion class.
Posted By: JibbSmart

Re: quaternions - 03/31/07 07:05

yeh the main thing isn't knowing exactly how they work, but what produces the results u are looking for
Posted By: ventilator

Re: quaternions - 05/11/07 06:48

i still don't understand quaternions. i can't imagine what they are but i borrowed some of your code:

Code:
void ent_getrbmatrix1(ENTITY *entity, D3DXMATRIX *mout)
{
// ignores entity scale -> not needed for rigid bodies -> collision shape size gets defined by bounding box

D3DXMATRIX mpan;
D3DXMatrixRotationZ(&mpan, entity->pan * DEG2RAD);

D3DXMATRIX mtilt;
D3DXMatrixRotationY(&mtilt, -entity->tilt * DEG2RAD); // -tilt?

D3DXMATRIX mroll;
D3DXMatrixRotationX(&mroll, entity->roll * DEG2RAD);

D3DXMATRIX mtranslation;
D3DXMatrixTranslation(&mtranslation,
entity->x * QUANTTOMETER,
entity->y * QUANTTOMETER,
entity->z * QUANTTOMETER);

D3DXMATRIX mtemp1; D3DXMatrixMultiply(&mtemp1, &mroll, &mtilt);
D3DXMATRIX mtemp2; D3DXMatrixMultiply(&mtemp2, &mtemp1, &mpan);
D3DXMatrixMultiply(mout, &mtemp2, &mtranslation);

//printmatrix("g1", mout);
}



void ent_getrbmatrix2(ENTITY *entity, float *mout) // does the same as ent_getrbmatrix1 but in a different way
{
VECTOR xaxis; vec_set(&xaxis, vector(1, 0, 0));
VECTOR yaxis; vec_set(&yaxis, vector(0, 1, 0));
VECTOR zaxis; vec_set(&zaxis, vector(0, 0, 1));

// ignores entity scale -> not needed for rigid bodies -> collision shape size gets defined by bounding box

vec_rotate(&xaxis, &entity->pan);
vec_rotate(&yaxis, &entity->pan);
vec_rotate(&zaxis, &entity->pan);

mout[0] = xaxis.x; mout[1] = xaxis.y; mout[2] = xaxis.z; mout[3] = 0;
mout[4] = yaxis.x; mout[5] = yaxis.y; mout[6] = yaxis.z; mout[7] = 0;
mout[8] = zaxis.x; mout[9] = zaxis.y; mout[10] = zaxis.z; mout[11] = 0;
mout[12] = entity->x * QUANTTOMETER;
mout[13] = entity->y * QUANTTOMETER;
mout[14] = entity->z * QUANTTOMETER;
mout[15] = 1;

//printmatrix("g2", mout);
}



void ent_setrbmatrix(ENTITY *entity, float *m)
{
D3DXVECTOR3 s;
D3DXQUATERNION q;
D3DXVECTOR3 t;

D3DXMatrixDecompose(&s, &q, &t, (D3DXMATRIX*)m);

//todo: check for gimbal lock?
entity->pan = RAD2DEG * atan2f(2 * (q.w*q.z + q.x*q.y), (1-2 * (q.y*q.y + q.z*q.z)));
entity->tilt = RAD2DEG * -asinf((2 * (q.w*q.y - q.x*q.z)));
entity->roll = RAD2DEG * atan2f(2 * (q.w*q.x + q.y*q.z), (1-2 * (q.x*q.x + q.y*q.y)));

entity->x = t.x * METERTOQUANT;
entity->y = t.y * METERTOQUANT;
entity->z = t.z * METERTOQUANT;
}



using quaternions seemed to be the easiest way to convert between eulers and matrices. this is needed if you want to use newton with lite-c.

do you really understand these three quaternion lines? is it 100% reliable this way? with google i found some similar conversion code with checks for gimbal lock?

it seems to work though!
Posted By: Tor

Re: quaternions - 05/11/07 07:43

Quaternions don't experience gimbal lock. That was part of the reason they came into such wide spread use (that and they produce smoother rotations around arbitrary axies).

http://www.gamasutra.com/features/19980703/quaternions_01.htm
Posted By: ventilator

Re: quaternions - 05/11/07 07:53

i know that quaternions don't experience gimbal lock.

but if you convert quaternions to eulers probably you have to check if the resulting eulers will have some kind if gimbal lock problem? i came across quaternion->euler conversion examples which did gimbal lock checks.

<edit>hm... i think i noticed some jittering now in very rare cases. looks like there has to be some kind of gimbal lock check like that:

Code:
	float st = -2 * (q.w*q.y - q.x*q.z);

if(fabs(st) > 0.9999)
{
draw_text("gimbal lock!", 10, 10, vector(255,255,255));

// but i have no clue what belongs here

}
else
{
entity->pan = RAD2DEG * atan2f(2 * (q.w*q.z + q.x*q.y), (1 - 2 * (q.y*q.y + q.z*q.z)));
entity->tilt = RAD2DEG * asinf(st);
entity->roll = RAD2DEG * atan2f(2 * (q.w*q.x + q.y*q.z), (1 - 2 * (q.x*q.x + q.y*q.y)));
}

</edit>
Posted By: JibbSmart

Re: quaternions - 05/11/07 12:03

i saw a few mention gimbal lock problems but i cannot imagine how that would happen, as it never tries to move from one euler to another, coz it converts to quaternions before rotating.

it could be referring to a couple of singularities as certain cumulative values approach +-90 because the conversion involves atan, which cannot use those values. this isn't a problem in my script.
Quote:

it seems to work though!


for this i am so glad! jcl said atan2 would be available in lite-c soon, and as soon as that happens my code will be slightly more accurate. good to see u know what you're doing, even if quaternions are confusing, enough to use them!

i find quaternions so valuable in writing my own physics, which i'm doing for a school project.

julz
Posted By: JibbSmart

[UPDATE] quatRad - 05/30/07 07:46

g'day,

in case any of you are using my quaternion code, i've uploaded a new zip (same link -- check my sig if you can't find it) which also contains quatRad.c as an alternative to quaternions.c.

quatRad uses doubles instead of floats, and uses radians instead of degrees. this update is due to a devastating recent discovery that objects won't rotate less than about 2.5 degrees (slightly smaller values get rounded up) in a frame. by using doubles and radians, quatRad will rotate as little as about 0.1 degrees in a frame.

enjoy,

julz
Posted By: JibbSmart

Re: [UPDATE] quatRad - 06/03/07 00:45

little update (if anyone's checking )

i've been tweaking it heaps, and quatRad will now let you rotate an object by approximately 0.02 degrees

the zip file hasn't been updated yet as i'm still working on little improvements, but i'll post here as soon as i've uploaded the latest version.

julz
Posted By: lyingmime

Re: [UPDATE] quatRad - 06/05/07 05:06

Excellent contribution!

I did some tests using your code--- converting level space to view entity space and trying to get an object in each to overlap. This provides good feedback on what is happening, as you know where the object should be (versus where it is) for any given position and orientation.

I encountered two problems. One is that there are singularities (where the object flips orthogonal to where it should be at a particular values), and I found it possible to lock the tilt in such a way that the object pretty much stays flipped until the tilt is changed. You might be able to solve this by adding a gimbal lock check to ang_for_quat. (I have to check, but this may only occur when rotating about multiple axes).

The other seems to occur only when rotating about multiple axes. Getting the orientations of the entities to match between the level space and view space should look something like this: (maybe there is a conceptual error on my part?)

Quote:

//get the entity orientation
vec_set(temp_angle2,temp_entity2.pan);
quat_for_ang(quaternion2,temp_angle2);
quat_to_unit(quaternion2);
//rotate by pan
vec_set(temp_angle1,vector(-camera.pan,0,0));
quat_for_ang(quaternion1,temp_angle1);
quat_to_unit(quaternion1);
quat_multiply(quaternion2,quaternion2,quaternion1); //also tried q1 by q2
//rotate by tilt
vec_set(temp_angle1,vector(0,-camera.tilt,0));
quat_for_ang(quaternion1,temp_angle1);
quat_to_unit(quaternion1);
quat_multiply(quaternion2,quaternion2,quaternion1);
//rotate by roll
vec_set(temp_angle1,vector(0,0,-camera.roll));
quat_for_ang(quaternion1,temp_angle1);
quat_to_unit(quaternion1);
quat_multiply(quaternion2,quaternion2,quaternion1);
//update entity orientation
ang_for_quat(temp_angle1,quaternion2);
vec_set(temp_entity1.pan,temp_angle1);




The entity orientations match well when the camera angles are in the first quadrant, but they flip or become constrained by 180 degrees (oscillatory) in the other quadrants. The behavior I was trying to duplicate looks like this:

Quote:

vec_set(temp_angle1,temp_entity2.pan);
vec_set(temp_angle2,vector(-camera.pan,0,0));
ang_add(temp_angle1,temp_angle2);
vec_set(temp_angle2,vector(0,-camera.tilt,0));
ang_add(temp_angle1,temp_angle2);
vec_set(temp_angle2,vector(0,0,-camera.roll));
ang_add(temp_angle1,temp_angle2);
vec_set(temp_entity1.pan,temp_angle1);




I'd love to see SLERP implemented, by the way!
Posted By: JibbSmart

Re: [UPDATE] quatRad - 06/05/07 06:32

i don't need SLERP for my project, so it isn't a big priority. however, i would be interested in implementing it later.

some of those problems sound like problems i've had when multiplying quaternions the wrong way:
to rotate quaternion2 by quaternion1, use "quat_multiply(quaternion2,quaternion1,quaternion2);"
it might actually be worth writing a function "quat_rotate(q1,q2)" which basically uses quat_multiply to rotate q1 by q2, but without any possibility of logic errors on the programmer's behalf. the quat_multiply system isn't quite intuitive when doing rotations, but it IS how a multiplication actually works.

after that changing the multiplications, try again (though you appear to mention trying this change...)

i'm not an expert so i don't really see how gimbal lock would be possible with quaternions.

i've seen some slight jittering or inaccuracies with certain angles, which i've mostly been able to associate with my custom atan2 -- jcl said a more accurate built-in atan2 will be implemented in a future update.

i'm glad you're giving this a go i'm mostly using quaternions for custom physics (the need for angle-axis rotation instead of pan/tilt/roll).

let me know if there are any more problems or they still persist -- i've been doing a lot of stuff with them and havevn't had any problems more than slight inaccuracies (quaternions remain accurate, just the euler representation that's off) and small jitters

julz
Posted By: xXxGuitar511

Re: [UPDATE] quatRad - 11/19/08 17:40

I'm converting quatRad to c-script for Portico.

If anyone has a need for a c-script version, I'll post it "as-is" with Julz approval
Posted By: Carlos3DGS

quaternions - 05/18/09 22:36

this looks like exacly what I need, too bad I don't understand it
Posted By: JibbSmart

Re: quaternions - 06/13/09 14:07

G'day Carlos,

Did you have a look at the readme? If I recall it explains how things work. It's been a while, though. The first post just has a .c file -- look a little further down to find a .zip with everything you need in it.

@xXxGuitar511 if you ever come back to this thread -- How'd the conversion go?

I was Googling myself and tried "JulzMighty", and came across this thread, scanned through for nostalgia's sake, and was quite surprised to find a post-2007 post, and another one in the last month!

Jibb
Posted By: LunaticX

Re: quaternions - 06/13/09 17:21

oh, you are still here JulzMighty =)
wanted to say thanks for your great job =)
Posted By: JibbSmart

Re: quaternions - 06/13/09 23:50

Thanks mate! smile

I was just looking through it expecting to see something embarrassing (I've done 1.5 years of uni since then). Nothing really stands out, thankfully. These holidays I'll probably fiddle around with it some more, utilize Lite-C's atan2, and look into slerp.

Jibb
Posted By: Gumby22don

Re: quaternions - 06/15/09 22:52

Julz - I was just thinking about putting some sort of quat into a project, and really should have thought to do a search on here.

I for one will be very interested in any update you make; keep us updated.

Don
have a great day
Posted By: JibbSmart

Re: quaternions - 06/17/09 03:59

I have exams for the next week and won't be able to do anything else until afterwards, but yesterday and today I was making an example for LunaticX and came across some problems and inaccuracies that I didn't pick up on back in the old days when I first wrote quatRad.c and quaternions.c.

I got the example working (really nothing special), and so next week I'll begin re-writing those functions, hopefully adding more (slerp), and making some nice, simple examples as well.

Jibb
Posted By: LunaticX

Re: quaternions - 06/17/09 04:49

dunno if this will help
but when i use something like this:

quaternize(45,pan_rel_to_me,rot_offset);
quat_to_unit(rot_offset);
quat_multiply(my_rotation,rot_offset,my_rotation);

ang_for_quat(tempang,my_rotation);
vec_set(my.[an,tempang);

.pan of entity is set to something like 45.012 instead of just 45, while tilt and roll stay unchanged
Posted By: JibbSmart

Re: quaternions - 06/17/09 06:16

I'll look into how I can improve the accuracy next week. I'm afraid I really have to get back to studying now frown.

Jibb
Posted By: JibbSmart

Re: quaternions - 06/17/09 13:04

Cool feature, very much worth looking forward to: ang_for_axis!

Kinda makes my Quaternions redundant. Good, though. The fewer wheels we have to re-invent, the better!

Jibb

EDIT: Ironically, we're also getting ang_for_matrix, which would've allowed me to convert from a Quaternion to an A7-native Euler angle without dealing with any trigonometry (and probably result in more accurate Euler representations of my Quats).
Posted By: JibbSmart

Re: quaternions - 08/13/09 05:57

Now that A7.80 is out I realized I mis-interpreted the use of ang_for_axis. It'll actually be very useful for converting Quaternions to Euler angles (which was the main issue with my old code), so I will fix and update my Quaternion code.

Hopefully this will happen next week, as the Shader Contest will be over.

Jibb
Posted By: Pappenheimer

Re: quaternions - 08/13/09 11:31

Maybe, you are the expert to answer this:
I want to turn a model's tilt and roll depending the normal of the surface traced beneath it. For a long while, I thought this is easily done by vec_to_angle:

c_trace(my.x, vector(my.x,my.y,my.z-1000), IGNORE_ME|IGNORE_PASSABLE);
vec_to_angle(temp, normal);

But, it obviously is not.

What's your advice?
Posted By: JibbSmart

Re: quaternions - 08/13/09 13:56

This is fairly easy to do with Quaternions -- though I can definitely understand your trouble trying to deal with this in Euler angles. In fact this is the type of situation where it's particularly useful to have Quaternions, but perhaps you don't want to convert to Quaternions just for this. That's okay, because the latest update (A7.80) adds ang_for_axis, which will be very helpful.

Here's how I've done it with Quaternions (should work without, and I've briefly outlined how to do it without Quaternions too) :

1. The player has a vector (just a local VECTOR) that always has its relative up vector (simple vec_rotate(up, my.pan) where "up" is (0, 0, 1)).

2. c_trace to get the normal (as you've done).

3. Find the angle between my "up" and the normal -- acos(vec_dot(up, normal)) -- we don't have to divide by the length of either vector since they are both unit vectors. This angle is how much we want to rotate our model.

4. Find the axis we want to rotate around by getting vec_cross(up, normal) (since the axis has to be perpindicular to both our starting vector and the vector we want to line it up with).

5. Lastly, to line the model up with the normal, we want to line the player up with the normal beneath it by rotating it by the angle we found in "3." around the axis we found in "4.". I did this with Quaternions (very easy that way), but if you want to do it without and you have the latest update, you can probably use "ang_for_axis(ANGLE* result, axis, angle);" to find the Euler version of the rotation, and then use ang_add to rotate your model by that amount.

I hope this is helpful. Let me know if you need a little more, and I'll be happy to help more, but as the shader contest deadline approaches Friday night I won't be able to help until Saturday.

On a side-note, a threw in a couple of small snippets from my Quaternion code into my contest entry, and have found ang_for_axis very useful for a very clean conversion back to Euler angles laugh

Jibb

EDIT: In step 1 I actually used my own Quaternion version of vec_rotate, because it would be inconsistent and slightly less stable to alternate between using the entity's Euler orientation and its Quaternion rotation. That won't change how you do it, though laugh
Posted By: Pappenheimer

Re: quaternions - 08/13/09 14:10

Thank's for the explanation, although I'm not sure whether I understood everything, but that's my part to inform myself.
For now, I wish you good progress with the development of your contest entry!
Posted By: EvGenius

Re: quaternions - 08/17/09 18:09

Hi, new function ang_for_axis is very helpful, but I am cant understand how about angle parameter: In manual you can read - Rotation about vAxis in 0..360 degrees counterclockwise. but it works well only in rads 0 ~ 3.14 ~ 6.28 o_O
so I convert angles in rads and after rotate, get some divergence with rotation
May be I am not correct use this function?
Posted By: JibbSmart

Re: quaternions - 08/17/09 22:46

Yes, that's right. The way I've utilised it uses rads as well. It's probably worth telling jcl in the "Blame the Manual" or "Bug" forums, as since vars are only accurate to 3 decimal places it'd probably be better in the 0-360 range like the manual describes.

I'll open a thread now laugh

Jibb
Posted By: EvGenius

Re: quaternions - 08/18/09 07:56

Ok =)
I am trying to test your 5 steps with this function, and I receive surprises- mistakes with function results =)
and now think, that maybe easy will be use quaternions)
Posted By: JibbSmart

Re: quaternions - 07/16/10 22:10

Having been asked about quaternions quite recently, I've decided to finally put up the latest version of my code:
Code:
// quaternions and quaternion operations -- by julian "jibb" smart

typedef struct QUATERNION {
	double w;
	double x;
	double y;
	double z;
} QUATERNION;

function quat_multiply(QUATERNION *end_result, QUATERNION *a, QUATERNION *b);
function ang_for_quat(ANGLE *a, QUATERNION *q);
function quat_for_ang(QUATERNION *q, ANGLE *a);
function quaternize(double a, VECTOR *v, QUATERNION *q);
function quat_to_unit(QUATERNION *q);
function quat_set(QUATERNION *q, QUATERNION *u);
function quat_add(QUATERNION *q, QUATERNION *u);
function quat_sub(QUATERNION *q, QUATERNION *u);
function quat_conjugate(QUATERNION *q, QUATERNION *u);
function vec_rot_by_quat(VECTOR *v, QUATERNION *q);
function vec_for_quat(VECTOR *v, QUATERNION *q);
function quat_nlerp(QUATERNION *q, QUATERNION *u, QUATERNION *v, double f);

// end_result is the result of quaternion "b" being rotated by quaternion "a"
function quat_multiply(QUATERNION *end_result, QUATERNION *a, QUATERNION *b) {
	// multiply two quaternions -- non-commutative!
	QUATERNION q;
	q.w = a->w*b->w - a->x*b->x - a->y*b->y - a->z*b->z;
	q.x = a->w*b->x + a->x*b->w + a->y*b->z - a->z*b->y;
	q.y = a->w*b->y - a->x*b->z + a->y*b->w + a->z*b->x;
	q.z = a->w*b->z + a->x*b->y - a->y*b->x + a->z*b->w;
	quat_set(end_result, &q);
}

function ang_for_quat(ANGLE *a, QUATERNION *q) {
	// finds a euler representation of a quaternion
	ang_for_axis(a, vector(q->x, q->y, q->z), acosv(q->w) * 2);
}

function quat_for_ang(QUATERNION *q, ANGLE *a) {
	q->w = cos((a->roll/2))*cos((-a->tilt/2))*cos((a->pan/2))+sin((a->roll/2))*sin((-a->tilt/2))*sin((a->pan/2));
	q->x = sin((a->roll/2))*cos((-a->tilt/2))*cos((a->pan/2))-cos((a->roll/2))*sin((-a->tilt/2))*sin((a->pan/2));
	q->y = cos((a->roll/2))*sin((-a->tilt/2))*cos((a->pan/2))+sin((a->roll/2))*cos((-a->tilt/2))*sin((a->pan/2));
	q->z = cos((a->roll/2))*cos((-a->tilt/2))*sin((a->pan/2))-sin((a->roll/2))*sin((-a->tilt/2))*cos((a->pan/2));
}

function quaternize(double a, VECTOR *v, QUATERNION *q) {
	// makes quaternion "q" contain the information in angle and vector
	double rotation = a/2;
	q->w = cos(rotation);
	q->x = v->x*sin(rotation);
	q->y = v->y*sin(rotation);
	q->z = v->z*sin(rotation);
}

function quat_to_unit(QUATERNION *q) {
	// converts a quaternion to a unit quaternion
	double magnitude = sqrt(q->x*q->x + q->y*q->y + q->z*q->z);
	if(magnitude == 0) {
		q->w = 1.0;
		return;
	}
	double scaleFactor = sqrt(1-q->w*q->w)/magnitude;
	q->x *= scaleFactor;
	q->y *= scaleFactor;
	q->z *= scaleFactor;
}

function quat_set(QUATERNION *q, QUATERNION *u) {
	// sets quat "q" to equal quat "u"
	q->w = u->w;
	q->x = u->x;
	q->y = u->y;
	q->z = u->z;
}

function quat_add(QUATERNION *q, QUATERNION *u) {
	// add two quaternions -- warning! does not equal a rotation!
	q->w += u->w;
	q->x += u->x;
	q->y += u->y;
	q->z += u->z;
}

function quat_sub(QUATERNION *q, QUATERNION *u) {
	// subtract the second quaternion from the first -- also does not equal a rotation!
	q->w -= u->w;
	q->x -= u->x;
	q->y -= u->y;
	q->z -= u->z;
}

function quat_conjugate(QUATERNION *q, QUATERNION *u) {
	// first quaternion equals the conjugate of the second quaternion 
	q->w = u->w;
	q->x = -u->x;
	q->y = -u->y;
	q->z = -u->z;
}

function vec_rot_by_quat(VECTOR *v, QUATERNION *q) {
	// rotate a vector by a quaternion
	VECTOR u;
	u.x = v->x*(q->w*q->w + q->x*q->x - q->y*q->y - q->z*q->z) + 2*(q->w*q->y*v->z + q->x*q->z*v->z + q->x*q->y*v->y - q->w*q->z*v->y);
	u.y = v->y*(q->w*q->w - q->x*q->x + q->y*q->y - q->z*q->z) + 2*(q->x*q->y*v->x + q->w*q->z*v->x + q->y*q->z*v->z - q->x*q->w*v->z);
	u.z = v->z*(q->w*q->w - q->x*q->x - q->y*q->y + q->z*q->z) + 2*(q->x*q->z*v->x - q->w*q->y*v->x + q->w*q->x*v->y + q->z*q->y*v->y);
	vec_set(v, &u);
}

function vec_for_quat(VECTOR *v, QUATERNION *q) {
	// find a unit vector pointing in the direction of the quaternion
	v->x = q->w*q->w + q->x*q->x - q->y*q->y - q->z*q->z;
	v->y = 2*(q->x*q->y + q->w*q->z);
	v->z = 2*(q->x*q->z - q->w*q->y);
}

function quat_nlerp(QUATERNION *q, QUATERNION *u, QUATERNION *v, double f) {
	// normalized linear interpolation of a quaternion between two others by a factor "f"
	QUATERNION r, s;
	quat_set(&r, u);
	r.w *= 1.0-f;
	r.x *= 1.0-f;
	r.y *= 1.0-f;
	r.z *= 1.0-f;
	quat_set(&s, v);
	s.w *= f;
	s.x *= f;
	s.y *= f;
	s.z *= f;
	quat_add(&r, &s);
	quat_to_unit(&r);
	quat_set(q, &r);
}


It has actually been a long time since I've tested it, and I'm well aware that contributions that come with examples are much more fun. I don't have the time at the moment. However, this should be a bit more efficient, and it adds normalised linear interpolation (sorry, not quite slerp, but it's faster and does a very similar job). I hope someone finds this useful.

Jibb
© 2024 lite-C Forums