ARTICLE2 - VECTORS
by SumFuka
Maths - Urgh, Eccch, YUCK ! But, like it or not, vectors are
crucial to quake coding. Every time you fire a rocket, you need
vectors. In fact, you could say that "if you don't wanna go down,
you betta get up with da vecta".
WHAT IS A VECTOR In a 3-dimensional world such as
quake, a vector is simply three numbers - an x component, a y
component and a z component. In q_shared.h at line 258 we can
see the C code definition of vec3_t : typedef float vec_t;
typedef vec_t vec2_t[2];
typedef vec_t vec3_t[3];
typedef vec_t vec4_t[4];
typedef vec_t vec5_t[5];
If you're new to C, a typedef associates a
complicated datatype with another name. Above, the first typedef
states that a single dimensional vector type, vec_t, is a
floating point number (e.g. 23.4567, as opposed to integers
like 23). The next typedef's define 2,3,4 and 5 dimensional
vector types. The most common vector type is the 3-dimenstional one,
vec3_t - it is simply an array of three vec_t's (in
other words, an array of three float's). These three floats
represent what we call the x, y and z
components.
THE ORIGIN Ok, the most basic vector is the
origin vector. Let's write it down like this : (0.0, 0.0,
0.0). The x, y and z components are all zero. Let's create an
origin vector in C code. vec3_t origin;
origin[0] = 0.0;
origin[1] = 0.0;
origin[2] = 0.0;
Now let's create a vector to represent the position of
player0, at (55.0, -23.0, 12.5). vec3_t player0pos;
player0pos[0] = 55.0;
player0pos[1] = -23.0;
player0pos[2] = 12.5;
Now, where is player0 standing ? Well, that depends on
something called...
THE CO-ORDINATE SYSTEM Let's assume that our quake
map is three dimensional (and multi-levelled). A space map, for
example. Let's also assume that the location (0.0, 0.0, 0.0) is
right on top of a platform somewhere in the middle of the map, with
bits of the map extending in directions both left and right,
forwards and back, and up and down. But, what defines 'left-right',
'forwards-backwards' or 'up-down' ?
Quake uses what we call a 'left handed 3d co-ordinate system'.
This means, take your left hand and point your arm forwards. Point
your thumb upwards, your index finger forwards and your middle
finger directly to the right. Let's assume that we are standing at
the origin in our space map, facing northwards.
Now, your middle finger is pointing in the direction of positive
x co-ordinates, your index finger is pointing to positive y
co-ordinates, and your thumb is pointing to positive z co-ordinates.
So, by holding out our 2 fingers and thumb (remember, LEFT hand!)
imagine that we are sitting at the origin looking north. We can see
that player0pos is a position to the right of us (x=55.0 is
positive), behind us (y=-23.0 is negative) and above us (z=12.5 is
positive).
If we wanted to suddenly teleport player0 64 units eastwards and
64 units up into the air, how would we do it ? Maybe : player0pos[0] += 64.0;
player0pos[2] += 64.0;
There is a better way to do this, however...
VELOCITY VECTORS Ok, all the vectors we've been
working with so far have been positional vectors - an (x,y,z)
position in the game world. There is also another use for vectors -
to represent velocity (Note: velocity is different to
speed, more later). How do we represent the velocity of
rocket that is moving eastwards, and 45 degrees upwards, at 900
units/second ? A vector.
This may be a little confusing, since the rocket will have a
position and a velocity, and both are represented by
vec3_t. vec3_t rocketpos;
vec3_t rocketvel;
rocketpos[0] = 0.0;
rocketpos[1] = 0.0;
rocketpos[2] = 0.0;
rocketvel[0] = 636.39;
rocketvel[1] = 0.0;
rocketvel[2] = 636.39;
// now let's 'move' the rocket, assume one second has gone by.
rocketpos[0] += rocketvel[0];
rocketpos[1] += rocketvel[1];
rocketpos[2] += rocketvel[2];
// another second goes by... etc
rocketpos[0] += rocketvel[0];
rocketpos[1] += rocketvel[1];
rocketpos[2] += rocketvel[2];
The result: after 1 second our rocket is located at
(636.39, 0, 636.39). But why did we use 636.39, not 900 ? Well the
answer to that lies in the fact that if you move in two dimensions
(in our case, eastwards and up) the total distance travelled is
measured along the long side of a right-angled triangle, where the
short sides of the triangle measure the distances moved in those two
dimensions. And we know that the long side of a right-angled
triangle is calculated from the good ol' c squared = sqrt (a
squared + b squared). So if a and b are 636.39, c works out to
be... 900. In three dimensions the maths gets even stickier.
DIRECTIONS Rather than worry about getting 636.39
from 900, there's a better way to work with a velocity of 900
units/second eastwards and upwards at 45 degrees. We simply break it
into two components - a direction and a speed.
Now for some definitions, a direction vector is a vector
in any direction where the length is precisely 1.0. For example,
(1.0, 0.0, 0.0) or (0.707, 0.0, -0.707) etc. We call any vector with
a length of precisely 1.0 a normalized vector.
A speed is a linear quantity (NOT a vector!!). For
example, 900.0, 0.0 or -450.0. A speed of 0.0 represents standing
still whilst negative speeds represent moving backwards.
Why is this easier ? Well, there is a simple equation that allows
us to 'move' an entity through space - endpos = startpos + speed
* direction. For example, let's model our rocket : vec3_t rocketpos;
vec3_t rocketdir;
float rocketspeed;
rocketpos[0] = 0.0;
rocketpos[1] = 0.0;
rocketpos[2] = 0.0;
rocketdir[0] = 0.707;
rocketdir[1] = 0.0;
rocketdir[2] = 0.707;
rocketspeed = 900.0;
// now let's 'move' the rocket, assume one second has gone by.
rocketpos[0] += rocketspeed * rocketdir[0];
rocketpos[1] += rocketspeed * rocketdir[1];
rocketpos[2] += rocketspeed * rocketdir[2];
But again, why is this easier ? Well, firstly - quake
provides us with several direction vectors at many points in
the code. For example, we quite often see the forward vector.
This vector is always pointing in the direction we are aiming. To
fire a rocket ? Give it a veclocity of forward multipied by
900. To fire a railgun, trace along the forward vector
for8192 units. Etc. As luck would have it, Carmack has given us a
mini-library of...
VECTOR FUNCTIONS Since vectors are used so much in
quake, it makes sense to define functions that take care of all the
common vector manipulation tasks (copying, adding, multiplying,
etc). Not only is it good practice to make use of these functions
(since they're already written, DUH!), it's important to realize
that the operators +, * etc CANNOT be used - this is
because vec3_t is an array, and the common mathematical
operators cannot be used on arrays in C (in the way we expect them
too, anyway). For example, // the stuff below is WRONG.
vector_a *= 100.0; // WRONG!
vector_c = vector_a + vector_b; // WRONG!
// we use functions to do addition, multiplication, etc.
VectorScale (vector_a, 100.0, vector_a); // CORRECT!
VectorAdd (vector_a, vector_b, vector_c); // CORRECT!
Here's a complete list (from q_shared.h line
357) :
- VectorSubtract(a,b,c) - subtract b from a, result is c
- VectorAdd(a,b,c) - add a to b, result is c
- VectorCopy(a,b) - copy a to b
- VectorScale(v,s,o) - make v s units long, result in o
- VectorMA(v,s,b,o) - make v s units long, add to b,
result in o
- VectorClear(a) - too easy
- VectorNegate(a,b) - flip a, result in b
- VectorSet(v,x,y,z) - another easy one
- Vector4Copy(a,b) - used for 4 dimensional vectors
- SnapVector(v) - round a vector to integer values
Got it all ? Notice how most of these functions take one,
two or three parameters and save the output in a second, third or
forth output parameter (the exceptions are VectorClear() and
VectorSet() ). For example, calling
VectorSubtract(a,b,c) will not modify a or b -
the result will be places in vector c. (Unless of course, we
say VectorSubtract(a,b,a), which is quite ok to do).
VectorMA(v,s,b,o) You might be wondering what this
function does. It's a useful function that combines a vector
multiply and a vector addition. It's used all over the place so it's
important to understand what it does. Let's go back to our "rocket
moving eastwards and upwards at 900 units/second" example, using the
vector functions... vec3_t rocketpos;
vec3_t rocketdir;
float rocketspeed;
VectorClear(rocketpos);
VectorSet(rocketdir, 0.707, 0.0, 0.707);
rocketspeed = 900.0;
// now let's 'move' the rocket, assume one second has gone by.
VectorMA(rocketpos, rocketspeed, rocketdir, rocketpos);
Notice that we did *exactly* the same thing as before,
but in half the amount of code. And it's much neater because we've
used the vector functions. VectorMA is particularly useful - get to
know it like an old girlfriend. Innit.
PUTTING IT ALL TOGETHER Ok, let's throw all this
into action. Open up g_missile.c and look at lines 390-393 :
VectorCopy( start, bolt->s.pos.trBase );
VectorScale( dir, 900, bolt->s.pos.trDelta );
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
VectorCopy (start, bolt->r.currentOrigin);
First, we assume that start is where the rocket
is firing from (just in front of the player, at waist height). And
dir is the direction that the player is facing (a normalized
vector, right ?). And a rocket moves at 900 units/second.
So the code simply does this :
- Copy the start vector to the bolt's trBase
- Multiply the dir vecotr by 900 and leave the result in
trDelta
- Snap trDelta (convert from floats to integers)
- Copy the start vector to the bolt's
currentOrigin
That's all there is to it. Have a
look through some other functions like fire_plasma and
weapon_railgun_fire - see if you can work out (for example)
how fast does plasma move ? What is the range of the railgun ? Or
the lightning gun ?
If you can answer all those questions - congratulations, you're a
vector geek !
|