Gamestudio Links
Zorro Links
Newest Posts
AlpacaZorroPlugin v1.3.0 Released
by kzhao. 05/20/24 20:05
Free Live Data for Zorro with Paper Trading?
by AbrahamR. 05/18/24 13:28
Change chart colours
by 7th_zorro. 05/11/24 09:25
Data from CSV not parsed correctly
by dr_panther. 05/06/24 18:50
AUM Magazine
Latest Screens
The Bible Game
A psychological thriller game
SHADOW (2014)
DEAD TASTE
Who's Online Now
3 registered members (Grant, dr_panther, AndrewAMD), 1,379 guests, and 6 spiders.
Key: Admin, Global Mod, Mod
Newest Members
Hanky27, firatv, wandaluciaia, Mega_Rod, EternallyCurious
19051 Registered Users
Previous Thread
Next Thread
Print Thread
Rate Thread
Quaternions and C-Script #120006
03/28/07 22:45
03/28/07 22:45
Joined: Mar 2006
Posts: 15
Kansas, USA
Q
quantum69 Offline OP
Newbie
quantum69  Offline OP
Newbie
Q

Joined: Mar 2006
Posts: 15
Kansas, USA
After reading through several posts and searching for info on quaternions, I thought I'd toss this in hoping it might help people who have asked questions specific to rotation and gimbal lock. I'll include the code below and a brief explanation and will answer any questions you have regarding it's use. I didn't find this subject covered very well for C-Script, so here goes.

Code:

function ql_qnormalize(&a) // &a must be 4-D vector of the form w,x,y,z quaternion
{
var f =0.0;var g =0.0;
f = sqrt(a[0]*a[0] + a[1]*a[1] + a[2]*a[2] + a[3]*a[3]);
a[0] /= f;
a[1] /= f;
a[2] /= f;
a[3] /= f;
}
function ql_vnormalize(&vec) // &vec is a 3-D vector of the form x,y,z
{
var f = 0.0;
f = sqrt( vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2]);
vec[0] /= f;
vec[1] /= f;
vec[2] /= f;
}
function ql_mul(&a,&b,&c) // [0]=w [1]=x [2]=y [3]=z
{
c[0] = (a[0] * b[0]) - (a[1] * b[1]) - (a[2] * b[2]) - (a[3] * b[3]);
c[1] = (a[0] * b[1]) + (a[1] * b[0]) + (a[2] * b[3]) - (a[3] * b[2]);
c[2] = (a[0] * b[2]) - (a[1] * b[3]) + (a[2] * b[0]) - (a[3] * b[1]);
c[3] = (a[0] * b[3]) + (a[1] * b[2]) - (a[2] * b[1]) + (a[3] * b[0]);
}
function ql_add(&a,&b)
{
a[0] = a[0] + b[0];
a[1] = a[1] + b[1];
a[2] = a[2] + b[2];
a[3] = a[3] + b[3];
}
function ql_sub(&a,&b) // returns in a-b = a
{
a[0] = a[0] - b[0];
a[1] = a[1] - b[1];
a[2] = a[2] - b[2];
a[3] = a[3] - b[3];
}
function ql_conjugate(&a,&b) // conjugate is the same as the unit inverse
{
b[0] = a[0];
b[1] = -a[1];
b[2] = -a[2];
b[3] = -a[3];
}
function ql_scale(&q,b)
{
q[0] = q[0] * b;
q[1] = q[1] * b;
q[2] = q[2] * b;
q[3] = q[3] * b;
}
function ql_AAvecToQ(&vec,theta,&q) // axis/angle to q. a=vector b=angle c=quaternion
{
var s;
s = sin(theta / 2);
ql_vnormalize(vec);
q[0] = cos(theta / 2); // w
q[1] = s * vec[0]; // x
q[2] = s * vec[1]; // y
q[3] = s * vec[2]; // z
ql_qnormalize(q);
}
function ql_qToVec(&q,&vec,&theta) // vec[0]=x vec[1]=y vec[2]=z theta=angle
{
var si;
theta = acos(q[0]);
si = 1.0 / sin(theta);
vec[0] = q[1] * si; // x = q.x * si
vec[1] = q[2] * si; // y = q.y * si
vec[2] = q[3] * si; // z = q.z * si
}
function ql_eulerToQ(&q,&angles) // z=phi y=theta x=psi (yaw, pitch, roll)
{
var cx;var cy;var cz;
var sx;var sy;var sz;
cz = 0.5 * cos(angles[0]);
cy = 0.5 * cos(angles[1]);
cx = 0.5 * cos(angles[2]);
sz = 0.5 * sin(angles[0]);
sy = 0.5 * sin(angles[1]);
sx = 0.5 * sin(angles[2]);

q[0] = (cz * cy * cx) + (sz * sy * sx);
q[1] = (cz * cy * sx) - (sz * sy * cx);
q[2] = (cz * sy * cx) + (sz * cy * sx);
q[3] = (sz * cy * cx) - (cz * sy * sx);
}
function ql_qToMat(&q,&m) // q[0]=w q[1]=x q[2]=y q[3]=z
{
var w;var x;var y;var z;
w = q[0]; x = q[1]; y = q[2]; z = q[3];

m[0] = 1 - (2 * y * y) - (2 * z * z);
m[1] = (2 * x * y) - (2 * w * z);
m[2] = (2 * x * z) + (2 * w * y);
m[3] = (2 * x * y) + (2 * w * z);
m[4] = 1 - (2 * x * x) - (2 * z * z);
m[5] = (2 * y * z) - (2 * w * x);
m[6] = (2 * x * z) - (2 * w * y);
m[7] = (2 * y * z) + (2 * w * x);
m[8] = 1 - (2 * x * x) - (2 * y * y);
}
function ql_MatToVec(&m,&vec) // vec[0]=x vec[1]=y vec[2]=z
{
var tx;var ty;var tz;
var vx;var vy;var vz;
vx = vec[0];vy = vec[1];vz = vec[2];
tx = (vx * m[0]) + (vy * m[1]) + (vz * m[2]);
ty = (vx * m[3]) + (vy * m[4]) + (vz * m[5]);
tz = (vx * m[6]) + (vy * m[7]) + (vz * m[8]);
vec[0] = tx;
vec[1] = ty;
vec[2] = tz;
}
function ql_QMV(&q,&m,&vec) // this is the composite function of the 2 functions above.
{ // it performs a quaternion to rotation matrix
var iw;var ix;var iy;var iz; // then
var tx;var ty;var tz; // it performs a 1x3 * 3x3 matrix multiply which is
var vx;var vy;var vz; // &vec * &m
iw = q[0]; ix = q[1]; iy = q[2]; iz = q[3]; //
vx = vec[0];vy = vec[1];vz = vec[2]; // &vec contains the rotated vector.
m[0] = 1 - (2 * iy * iy) - (2 * iz * iz); //
m[1] = (2 * ix * iy) - (2 * iw * iz); // ...translate and scale this vector and that's it
m[2] = (2 * ix * iz) + (2 * iw * iy); //
m[3] = (2 * ix * iy) + (2 * iw * iz); //
m[4] = 1 - (2 * ix * ix) - (2 * iz * iz); //
m[5] = (2 * iy * iz) - (2 * iw * ix); //
m[6] = (2 * ix * iz) - (2 * iw * iy); //
m[7] = (2 * iy * iz) + (2 * iw * ix); //
m[8] = 1 - (2 * ix * ix) - (2 * iy * iy); //
tx = (vx * m[0]) + (vy * m[1]) + (vz * m[2]); //
ty = (vx * m[3]) + (vy * m[4]) + (vz * m[5]); //
tz = (vx * m[6]) + (vy * m[7]) + (vz * m[8]); //
vec[0] = tx;
vec[1] = ty;
vec[2] = tz;
}




First off, you need to create a vector. This vector represents the quaternion and points (x,y,z) in space can be rotated around this quaternion vector. The benefit of quaternion rotation is that it doesn't have to line up with any of the 3 major axis (the +X/-X, +Y/-Y, +Z/-Z), in fact that's a big part of the power of using quaternions. In normal Euler angle rotations, you are always rotating about a major axis. Gimbal lock is a problem of Euler rotations, since quaternions have no such problem they are great for rotating things in the world. Remember though, these functions are not replacing the the built-in model handling rotations, those are part of the engine and how Conitec handles it internally. What these functions are for is primarily rotating positions about an arbitrary axis, the quaternion. I'll try to give a short example.

Create a vector, assume it's origin is at position 0,0,0.
Code:

qvec[0] = cos(my.skill80) * cos(my.skill81);
qvec[1] = sin(my.skill80) * cos(my.skill81);
qvec[2] = sin(my.skill81);



In this example I'm using skill80 as the PAN (or azimuth) angle and skill81 as the TILT (or elevation) angle. These calculations are standard spherical rotations, however I should note that the standard calculation uses the elevation(tilt) angle starting from the vertical (or +Z axis), this calculation is generally modified (which has been done) so the elevation angle starts from the horizontal plane (the X/Y plane).

qvec[] is just an array var used to hold the result.

We call ql_AAvecToQ sending in this qvec[] array, a rotation value (I'll explain in a moment) and another array var to hold the quaternion we create.

i.e. qlAAvecToQ(qvec, 0, quat1);

The definition of quat1 is simply a 4 element array var.
var quat1[4];

The 0 (zero) as a parameter to the function represents the angle of rotation around the quaternion. This is where quaternion explanations sometimes get people confused. Think of a vector that points in the +Z direction, so it's pointing straight up. Now think of the pan angle, which would be the angle of rotation around the +Z axis. What would happen if took the vector that represents the +Z axis and added a 4th element to that vector array to include the pan angle. Well, a vector that is parallel to the +Z axis would be 0,0,1 (normalized) and say we have a pan angle of 30 degrees. Let's put the pan angle as the first element of the array and move the direction vector into the other positions.

In essence you'd have a 4 element vector that looked like this:
my_vector[4] = 30,0,0,1; // pan angle, (X, Y, Z) vector components

A quaternion is a 4-element vector. It's components are labeled W,X,Y,Z. Where W represents the rotation around the vector given by the X,Y,Z. Our my_vector[4] example could be turned into a real quaternion by sending the vector portion of it to the appropriate function and sending the pan angle as well in the proper parameter position.

var my_newvec[3];
my_newvec[0] = my_vector[1];
my_newvec[1] = my_vector[2];
my_newvec[2] = my_vector[3];

.
ql_AAvecToQ(my_newvec, my_vector[0], quat1);
.
The elements of a quaternion are not simply the angle followed by the simple vector components. The way a quaternion is created is beyond the scope of this post, but there are numerous web sites with tons of info on the subject. That's why the example my_vector[4] can't be used as-is for calculations.
.
Once ql_AAvecToQ() has been called, we have a new quaternion stored in the quat1[4] variable. You may ask, "What now?" A good question. Let me answer it.
.
A quaternion by itself has no use. It's a vector pointing in space and has an angle of rotation value.
.
Remember above how we can think of rotating around the +Z axis using the PAN angle? Well, what if we could rotate the +Z axis in space? That's what the quaternion represents. A vector pointing to any arbitrary point in space, and we can rotate points around that axis. That is the ultimate power of the quaternion, arbitrary rotation around the quaternion's axis.
.
How to do that?
.
You multiply the quaternion and a position. The new position will be the original position rotated around the quaternion axis by the angular amount stored in the quaternion. Cool eh?
.
Let's create a point in space by simply creating a 3-element array (a vector).
.
var my_point[3] = 5.2, 6.1, 1.2;
.
So we have our point, which is actually a vector, hanging there in space. We want to rotate our point around the quaternion axis. There are 2 ways I've given in the functions to do this. First, you can call 2 separate functions or call just 1 function. I merged the 2 functions into one called ql_QMV(). QMV stands for Quaternion-(times)-Matrix-(times)-Vector. QMV takes as input; a quaternion, a 9-element array which is used for the matrix, and a 3-element vector --- this is the vector(point) to be rotated and is used as output to hold the result. Remember the result will be stored in the input vector.
.
var my_matrix[9]; // used by the quaternion functions
.
ql_QMV(qvec, my_matrix, my_point);
.
my_point will be rotated around the qvec axis by the angle stored in qvec. That's the short and simple of performing a rotation using a quaternion. In my next post I'll try to elaborate a little on how better to use this in practice.
.
Quantum Mechanic
Better living at the subatomic level.

Last edited by quantum69; 03/28/07 23:59.

------------------------------------
Quantum Mechanic
Better living at the subatomic level
Re: Quaternions and C-Script [Re: quantum69] #120007
03/28/07 23:36
03/28/07 23:36
Joined: Mar 2006
Posts: 15
Kansas, USA
Q
quantum69 Offline OP
Newbie
quantum69  Offline OP
Newbie
Q

Joined: Mar 2006
Posts: 15
Kansas, USA
To continue on this thought...

How would you use this over time? Glad you asked.

In the action loop of an entity you would want to update the quaternions vector (so the axis you rotate around changes) or the quaternions rotation value (so objects are rotated more or less around the quaternion axis). I'm going to include a test entities action code and explain the pertinent parts.

Code:

action quaternion_test()
{
my.qe1phi = 0.0;my.qe1theta=0.0;
my.qe2phi = 0.0;my.qe2theta=0.0;
my.qe3phi = 0.0;my.qe3theta=0.0;
my.qeAlt = 100;
my.flag1 = off; my.flag2 = off;
while(1)
{
// quaternion testing

qvec1[0] = cos(my.qe1phi) * cos(my.qe1theta); // spherical rotation calculations
qvec1[1] = sin(my.qe1phi) * cos(my.qe1theta); // these are used to create the 3 axis/angles vectors
qvec1[2] = sin(my.qe1theta);

qvec2[0] = cos(my.qe2phi) * cos(my.qe2theta);
qvec2[1] = sin(my.qe2phi) * cos(my.qe2theta);
qvec2[2] = sin(my.qe2theta);

qvec3[0] = cos(my.qe3phi) * cos(my.qe3theta);
qvec3[1] = sin(my.qe3phi) * cos(my.qe3theta);
qvec3[2] = sin(my.qe3theta);

if(my.flag1 == on)
{
qvar1 += 1 * time;
qvar1 %= 360;
//qvar1 = 1;
}
if(my.flag2 == on)
{
qvar2 += 1 * time;
qvar2 %= 360;
}

ql_AAvecToQ(qvec1,qvar1,qtest1); // create a quaternion (qtest1) from the qvec1,qvar1 (axis/angle) pair
ql_AAvecToQ(qvec2,qvar2,qtest2); // create a quaternion (qtest2) from the qvec2,qvar2 (axis/angle) pair

ql_mul(qtest1,qtest2,qtest3); // creates a new quaternion (qtest3) from qtest1 * qtest2
ql_qnormalize(qtest3); // re-normalize the result (qtest3)

ql_QMV(qtest3,qmat1,qvec3); // create the new position from the quaternion (qtest3)

my.x = quatpos.x + (qvec3.x * my.qeAlt); // tranlates and scales the resulting vector (qvec3) so that the
my.y = quatpos.y + (qvec3.y * my.qeAlt); // 3-tuple (x,y,z) values are where we need them.
my.z = quatpos.z + (qvec3.z * my.qeAlt); // this concludes the proof of concept GS usage of quaternions
// --- this places this object (my) at the new coords [x,y,z]
vec_set(temp,qvec2.x); //
vec_scale(temp,my.qeAlt); //
vec_add(temp, quatpos.x); //
vec_set(quat_mkr.x,temp); //

vec_set(temp,vector(qvec1[0],qvec1[1],qvec1[2]));
vec_add(temp,quatpos.x);
vec_sub(temp,quat_axis1.x);
vec_to_angle(quat_axis1.pan,temp);

vec_set(temp,vector(qvec2[0],qvec2[1],qvec2[2]));
vec_add(temp,quatpos.x);
vec_sub(temp,quat_axis2.x);
vec_to_angle(quat_axis2.pan,temp);

vec_set(temp,vector(qvec3[0],qvec3[1],qvec3[2]));
vec_add(temp,quatpos.x);
vec_sub(temp,quat_axis3.x);
vec_to_angle(quat_axis3.pan,temp);

// this segment makes the camera follow and look-at a target, not really useful on a static object, but great for moving objects
/*
vec_set(temp,qvec3.x);
vec_scale(temp,300);
vec_add(temp,quatpos.x);
vec_set(camera.x,temp);
vec_set(temp,quatpos.x);
vec_sub(temp,camera.x);
vec_to_angle(camera.pan,temp);
*/
if(toqli.visible==on)
{
str_for_num(toqli.string[0],qvar1);
str_for_num(toqli.string[1],qvar2);
str_for_num(toqli.string[3],qtest3[0]); // the final quaternion
str_for_num(toqli.string[4],qtest3[1]);
str_for_num(toqli.string[5],qtest3[2]);
str_for_num(toqli.string[6],qtest3[3]);
str_for_num(toql.string[0],qmat1[0]); // show matrix contents
str_for_num(toql.string[1],qmat1[1]);
str_for_num(toql.string[2],qmat1[2]);
str_for_num(toql.string[4],qmat1[3]);
str_for_num(toql.string[5],qmat1[4]);
str_for_num(toql.string[6],qmat1[5]);
str_for_num(toql.string[8],qmat1[6]);
str_for_num(toql.string[9],qmat1[7]);
str_for_num(toql.string[10],qmat1[8]);
str_for_num(toqlo.string[0],qvec1[0]); // the vectors
str_for_num(toqlo.string[1],qvec1[1]);
str_for_num(toqlo.string[2],qvec1[2]);
str_for_num(toqlo.string[4],qvec2[0]);
str_for_num(toqlo.string[5],qvec2[1]);
str_for_num(toqlo.string[6],qvec2[2]);
str_for_num(toqlo.string[8],qvec3[0]);
str_for_num(toqlo.string[9],qvec3[1]);
str_for_num(toqlo.string[10],qvec3[2]);
}
wait(1);
}
// end quaternion testing
}



What you're seeing me do here is to create 3 vectors at the top of the loop, then I increment a couple of global counters. These counters represent the quaternion rotation values for 2 separate quaternions. You see I recreate the quaternions every iteration of the loop. This is necessary because I am changing the quaternion rotation values and external to this action I am altering the 3 vectors pan/tilt (phi/theta) angles so I can move the quaternions around as well.

Next is an example of what makes quaternions really cool!!! Quaternion multiplication with another Quaternion. This may not sound like much, but this allows for things like Linear Interpolation, which is the steady rotation of a quaternion. It has other uses, but again, there are tons of resources on the subject available on the net.

You'll next see that I am moving the object this action is assigned to. I have several helper objects(entities) loaded elsewhere that I place in the world as well. I said earlier this was my example code and I was using it to demonstrate to a friend what a quaternion is and how it is applied. I move my 3 helper objects then display in a few text objects a bunch of output.

This demonstrates updating a quaternion, multiplying quaternions, moving objects and displaying information.

One thing to note is the call to ql_qnormalize(). Quaternions are 4-element vectors and MUST always be normalized (length of the vector is 1). A non-normalized quaternion is no longer considered a quaternion but rather something undefined. The function ql_AAvecToQ does 2 things, it normalizes the input vector and then normalizes the quaternion before exiting. I have since streamlined my code to include all the relevant code down into a function so no other function calls are necessary, but for the sake of clarity I decided to post this pre-optimized version so you could see what was happening.

The big thing about the Conitec rotations is that they are based on Z-X-Z rotations and not true 3-axis Z-Y-X rotation. If you have a plane at the origin (0,0,0) and it's facing along the positive +X axis (pan = 0), you can't roll the plane then change it's pan so that the nose follows the angle of the roll. The nose always rotates around the vertical +Z axis. These quaternions functions do not overcome that limitation. It would take an external DLL that handles model rotation and probably the graphics pipeline as well to overcome the limitation. I've seen several posts asking about 6-degrees of freedom rotation. The GStudio engine doesn't do it, or at least the limited trial 6.2 version doesn't. For real 6D freedom it would have to allow arbitrary axis rotation in the engine code, which is where quaternions shine.

There are numerous ways to use quaternions, my example was only to demonstrate both GS as a potential design platform and to give a friend a visual aid in understanding some math. My demo action is not the best way to use them, it's not the worst way to use them, it's just "a" way to use them.

As for getting and testing the latest incarnation of GS, I've thought about it, but after butting up against a lot of limited functionality I can only imagine similar limits would be more so. I've been trying to get a small dev house to purchase the full, high-end package so I can give the whole smash a whirl but they're not entirely impressed with GS, not enough to fork out the $900. It's got some promise, but I can't see living well with the wife were I to spend that kind of cash on a potential useable platform. Maybe next year. In the meantime as I run across posts asking for help with some of the more esoteric aspects of game programming, I'll try to lend a hand with code and explanation.

I do make some assumptions in my writing. I assume you know how to code C-Script and that calling functions, creating actions, etc, is old-hat to you. Those subjects are covered in the forum as well. If you do have a specific question on how to use a quaternion, speak up. Right now there is a C-Lite quaternion thread and I'm letting that gentleman explain his snippets.

I hope these 2 posts answer some questions, give some examples and useable code to the GS community.

Quantum Mechanic
Better living at the subatomic level.

Last edited by quantum69; 03/28/07 23:54.

------------------------------------
Quantum Mechanic
Better living at the subatomic level

Moderated by  HeelX, Lukas, rayp, Rei_Ayanami, Superku, Tobias, TWO, VeT 

Gamestudio download | chip programmers | Zorro platform | shop | Data Protection Policy

oP group Germany GmbH | Birkenstr. 25-27 | 63549 Ronneburg / Germany | info (at) opgroup.de

Powered by UBB.threads™ PHP Forum Software 7.7.1