Basic OpenGL for the Newbie, Part 2

BlitzMax Forums/BlitzMax Tutorials/Basic OpenGL for the Newbie, Part 2

PaulJG(Posted 2005) [#1]
Keyboard Control / Basic Movement

Let's take control of the world and give the user a little bit of interaction, in this tutorial we will add keyboard control to the tank demo. As before I will just be explaining the lines of code that have been changed or added.

You can download the source code and precompiled exe here.

I've used the keys as follows, but of course you alter them to whatever you prefer:

Q = Spin the camera up over the object
A = Spin the camera down

Z = Rotate the whole tank clockwise
X = Rotate the tank anticlockwise

< = Rotate just the turret and gun sections clockwise
> = Rotate the turret and gun anticlockwise

So how do you add keyboard scanning into the demo?
Glut makes the job very easy, just like the IDLE procedure we store all the keyboard routines in a special procedure called KEYBOARD. Let's take it one step at a time.

First we declare the keyboard function:

void keyboard(unsigned char,int,int);

Right down the bottom of the listing you will find the new procedure, everything that involves the keyboard must be packed into this procedure.

void keyboard(unsigned char key,int x,int y)
{
switch (key){

case 'Q':
case 'q':
cam.rotx=cam.rotx+2; break;

case 'A':
case 'a':
cam.rotx=cam.rotx-2; break;

case 'Z':
case 'z':
object[0].roty-=1.5f;
if (object[0].roty<0) object[0].roty=360.0f;
break;

case 'X':
case 'x':
object[0].roty+=1.5f;
if (object[0].roty>360) object[0].roty=0.0f;
break;

case '<':
case ',':
object[1].roty+=1.5f;
if (object[1].roty>360) object[1].roty=0.0f;
break;

case '>':
case '.':
object[1].roty-=1.5f;
if (object[1].roty<0) object[1].roty=360.0f;
break;
}
}

Hopefully all of the above should make sense to you, as you can see the variable KEY is used by GLUT to hold any key-presses that may happen. Then its just a case of acting on whatever key was pressed using the switch command.

Here's a rundown of what I'm doing:

If you press the Q or A key then the camera will rotate around the object

The Z or X key will change the rotate value in shape 0 (the body)

Pressing the < or > key will rotate the shape 1 (the turret) - as we did not use a glMatrixPush/glMatrixPop commands the gun will also be rotated to whatever setting the turret is.

The only part of the keyboard routine still left, is the actual code that informs GLUT that we have a keyboard routine. This is done in the MAIN routine with the command 'glutKeyboardFunc(keyboard)'. Once GLUT knows we have keyboard routine set up, it will automatically track the keyboard for key-presses jumping down to the keyboard procedure every screen update.

glutKeyboardFunc(keyboard);

Now that we have keyboard control there's really no need for the autorotate commands in the IDLE procedure so I deleted them, this left me with just the 'glutPostRedisplay' command.

void idle_func (void)
{
glutPostRedisplay ();
}

I wanted the tank to behave more like a tank so I joined up the rotation of the body with the turret, that is to say that although you can move the turret independently it will still rotate when the body is rotated. (try the EXE demo for a practical demo - it will make sense, honest !)

To do this all I did was to add the rotation values of shape 0 (the body) to the rotation values of the shape itself.

glRotatef(object[0].roty+object[1].roty,0.0f,1.0f,0.0f);

Lastly I decided to delete the rotate and translation commands for the gun as they were not needed. As the turret was rotated so would the gun. (the knock-on effect remember that ?)

Of course if we were to allow the gun object to move independently we would have to encase the turret section with the glPushMatrix/glPopMatrix commands and then use the rotate and translation commands for the gun object.

By now I expect a few of you are having bad slowdown problems, what we need to do is use pure windows code that will bypass GLUT and give us full access to our 3D graphics card. Don't worry its not as hard as you may be thinking....


PaulJG(Posted 2005) [#2]
Simple Texture Mapping

Time for the big one, I'm sure you've waiting for this tutorial - at long last I will show you how to add textures to your object. Without textures a lot of today's games would look very poor, but there is a huge price to pay for a game packed with lifelike graphics...Processing Power.

So many games today contain more and more textures to brighten up there 3D world, but at the expense of running slower and slower on our home PC. Unless you can afford to upgrade every few months, you will notice that new games will effect your system.

BUT, used properly and in moderation - texture maps can really boost visuals in any game. Opengl has full support for many kinds of methods, but I will be using them in the easiest way possible. (It works for me !)

Firstly we must decide what format to use to design our textures in...

RAW

Yes, the most basic of format will be our work horse. I'm not sure what paint packages support it, but I`m sure most will. (I use PaintShop Pro 7)

For now all we will be texturing is a small flat square, consisting of just 4 vertex.

Size
Most 3d Graphics Cards will not accept any old size, so it is best to use a general size that is fully compatible.

This limits you to:

32x32
64x64
128x128
256x256
Of course the texture you design doesn't have to cover the whole of the area, it is possible to put as many shapes that will fit into the same texture area. If you're worried that the sizes are too small don't be, nearly all 3D graphic cards will stretch the texture onto the object and use a Bilinear or Trilinear filter on it (giving it a clear image).

Once you have designed your texture you must save it, using Paintshop Pro 6 - these are the steps that I use.

After selecting a name, choose OPTIONS before saving.

Make sure that the HEADER SIZE = 0, and the FLIP TOP TO BOTTOM is unchecked (empty)

INTERLEAVED (RBG) is selected as the save format

ORDER of RGB is selected

I use the RAW format because it is the easiest to load into my OpenGL projects. There's no header info or complicated file compression to worry about, just open the file and load in all the bytes you can.

The only downside is that if you want to re-edit the file in the paint program you have to input the size of the image as this info is not stored in a header. So if you use different size textures make sure you make a note of the sizes or name them with some kind of clue to the size. It will probably ask you what Color Channel to load in as well, make sure you choose 3 channel (RGB).

You can load in the example texture for an example for all of the above instructions, it is stored in the directory TEXTURES with the filename TEST.

Anyway.. Back to business.

Let's have a look at the main sections that have changed in project file (Don't forget the 'setupogl.h' file must still be linked to any project you are working on)

You can download the source and exe files here

#include <windows.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include "setupogl.h"
#include <stdlib.h>
#include <stdio.h>

The only changes here is the inclusion of the 'stdlib.h' and 'stdio.h' - library files, needed for the file routines later.

#define MAX_NO_TEXTURES 1

The DEFINE statement is used to hold the total number of textures we will be using, in this case just the one.

float roty;

I used this variable to hold the rotation angle value, so we can spin the object.

GLuint texture_id[MAX_NO_TEXTURES];

This important command informs OpenGL how many textures it is to accept.

void load_texture ( char *file_name, int tex_size )
{
GLubyte *raw_bitmap ;
FILE *file;

if (( file = fopen(file_name, "rb"))==NULL )
{
MessageBox(NULL,"File Not Found.",
"FILENAME ERROR", MB_OK | MB_ICONINFORMATION);
exit ( 1 );
}

raw_bitmap = (GLubyte *)malloc(
tex_size * tex_size * 16 * (sizeof(GLubyte)));

if ( raw_bitmap == NULL )
{
MessageBox(0, "Cannot allocate memory for texture.",
"MEMORY ERROR", MB_OK | MB_ICONINFORMATION);
fclose ( file );
exit ( 1 );
}

fread(raw_bitmap, tex_size * tex_size * 16, 1, file);
fclose(file);

// Set Filtering type
glTexParameteri ( GL_TEXTURE_2D,
GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri ( GL_TEXTURE_2D,
GL_TEXTURE_MIN_FILTER, GL_LINEAR );

glTexImage2D(GL_TEXTURE_2D,0,3,tex_size,tex_size,
0,GL_RGB,GL_UNSIGNED_BYTE,raw_bitmap);

// Free up the array
free ( raw_bitmap );
}

Just a standard C function that opens the file and reads in the data to a temporary buffer array called 'raw_bitmap'.

glTexParameteri ( GL_TEXTURE_2D,
GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri ( GL_TEXTURE_2D,
GL_TEXTURE_MIN_FILTER, GL_LINEAR );

These two functions set the various parameters we can have for the texture, the first command sets the options for the texture when it is to be scaled smaller than what the texture was originally defined as.

The second command is of course for what happens when it is scaled larger then the original size.

Notice the use of the 'GL_LINEAR' parameter, this gives us the best quality that it can give, we could have used 'GL_NEAREST' - which is the quickest method to render but doesn't look too good. You could use a combination of the 2, making the texture look its best close up but not to bother when far away in the distance.

glTexImage2D(GL_TEXTURE_2D,0,3,tex_size,
tex_size,0,GL_RGB,GL_UNSIGNED_BYTE,raw_bitmap);

This command loads the texture from the raw_bitmap array into memory.

Finally we free up the temporary buffer array

void Init(void)
{

glClearColor(0.0f ,0.0f ,0.0f ,0.0f);
glClearDepth(1.0f);

//glEnable(GL_CULL_FACE);

glDepthFunc(GL_LEQUAL);
glEnable(GL_DEPTH_TEST);

glEnable ( GL_TEXTURE_2D );
glPixelStorei ( GL_UNPACK_ALIGNMENT, 1 );
glGenTextures ( MAX_NO_TEXTURES, texture_id );
glBindTexture ( GL_TEXTURE_2D, texture_id[0] );
load_texture ( "textures/test.raw", 128 );

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0f,(GLfloat)640/(GLfloat)480,
0.1f,100.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

}

Not too many changes here, the first thing you will notice is that I've deleted the CULL_FACE command, this is because for this example I want the back of the texture to show through the back face of the square (it will be reversed).

glEnable ( GL_TEXTURE_2D );
glPixelStorei ( GL_UNPACK_ALIGNMENT, 1 );
glGenTextures ( MAX_NO_TEXTURES, texture_id );
glBindTexture ( GL_TEXTURE_2D, texture_id[0] );

These commands are used to setup OpenGL for textures:

glEnable ( GL_TEXTURE_2D );

Informs OGL that we wish to use 2D texturemaps.

glPixelStorei ( GL_UNPACK_ALIGNMENT, 1 );

Used to setup the format of the texture.

glGenTextures ( MAX_NO_TEXTURES, texture_id );

This command builds the textures.

glBindTexture ( GL_TEXTURE_2D, texture_id[0] );

This points OGL to the first position in memory of where to store the texture.

load_texture ( "textures/test.raw", 128 );

This function calls the load_texture routine to load in the texture that we have specified. It uses the perimeters of the name or/and the local directory and the size of the texture. (in this case 128x128)

int Display(GLvoid)
{
Idle();
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glLoadIdentity();

glTranslatef(0,0,-5);
glRotatef(roty,0.0f,1.0f,0.0f);

glBindTexture ( GL_TEXTURE_2D, texture_id[0] );

glBegin(GL_QUADS);

glTexCoord2f(0,0);
glVertex3f(-1, 1, 0);

glTexCoord2f(0,1);
glVertex3f(-1, -1, 0);

glTexCoord2f(1,1);
glVertex3f(1, -1, 0);

glTexCoord2f(1,0);
glVertex3f(1, 1, 0);

glEnd();

return TRUE;
}

Now all that is left is the drawing of the texture, before you can draw any texture on to the shape - you must first point OGL to the memory location it is stored at.

glBindTexture ( GL_TEXTURE_2D, texture_id[0] );

As you can see this sets OGL to find the first texture in memory.

Now we define our object as normal using the vertex commands, for every vertex point there must also be a texture point.

In the case of the example - we will point every vertex point, to the corners of the texture map. It is very important that each corner corresponds to the correct corner of the texture map, otherwise you could see a nasty mess. This will probably be one of the most annoying processes of OpenGL programming.

As the points of my square are defined as top left, bottom left, bottom right and top right - so must the co-ordinates of the texture.

We do this by using the glTexCoord2f(x,y); command. The texture runs from 0,0 (top left) to 1,1 (bottom right).

So we define the texture co-ords as:

glTexCoord2f(0,0); // Top Left
glTexCoord2f(0,1); // Bottom Left
glTexCoord2f(1,1); // Bottom Right
glTexCoord2f(1,0); // Top Right

That's it for the texture, consider it mapped !

The Keyboard() function is not used as we can press the ESCAPE key to quit the program, Lastly the Idle() function just increases the rotation value - making the shape spin.

AND NOW FOR SOMETHING NEW....

When you've finally calmed down with the excitement of your first textured object, let's try putting everything we've learnt into a more practical example.

How about we make a Borg Cube from the TV series Star Trek? It's a nice and easy shape for us to build (a cube) - all we will need is a texture that we can display on all 6 sides.

You can download the Borg project files here

If you load the project code you will see that the only line I've changed is the texture loading command filename (to load the texture 'cube' ). I've added the extra vertex commands and texture co-ords that define the cube object, plus a few extra lines of code to spin the cube in the X axis as well as the Y.


PaulJG(Posted 2005) [#3]
Advanced Texture Mapping

For the simple texturing examples we've been using, 'QUADS' corner-to-corner mapping is fine but really isn't much use for precise, detailed texturing.

It's nearly always best to use TRI mapping (3 points), as most 3d graphic cards are designed to blast these onto the screen in the fastest time possible.

So from now on any square 4 pointed shape will be made up of 2 triangle objects.

Here's an example:

---------
|\ |
| \ |
| \ |
| \|
---------

We don't have to map from each corner of the texture, infact we can use any position on it we like. As stated in the previous tutorial the texture coordinates run from 0,0 (top-left) to 1,1 (bottom-right).

So, if we wanted to find the middle position then we would have the coordinates: 0.5, 0.5

If possible always fill any blank areas on a texture map with as many extra details as possible to conserve memory from loading excessive textures. (Cram on everything that you can!)

For instance the character models in 'Quake' use 1 texture map for every character, that includes the front and back details.

So how do you find the texture positions?
Unless you want to use the old Trial & Error method of hand plotting, then you will have to use an application program such as Blender. In my opinion these programs are quite complicated to use and don't really give out the required coordinates without a lot of setting up beforehand.

Just to make life a little easier for myself, I've written an APP just for the coordinate finding purpose.

You can download it here - see the enclosed ReadMe file for instructions

An Added Bonus
Remember we used the 'glColor3f' command to colour the faces of an object, we can still do this - But, it has the effect of tinting the texture the selected colour. If you do decide to combine textured surfaces with plain coloured surfaces then you should always set the colour to WHITE (255,255,255) before texturing, which stops the tinting process.


PaulJG(Posted 2005) [#4]
Advanced Camera Movement (in a 3D Environment)

If you've managed to get this far with the tutorials then 'Well Done', this is without doubt going to be the hardest going tut. But Stick with it, Examine the source and keep Re-reading as this tut is very important to any OpenGl Games Programmer.

We are going to take everything well have previously learnt, and combine it to produce a basic 3D Engine that will give you total control inside your 3d world. There are still limitations to what we can achieve with this code, but it is a good base for adding extras and producing the next ultra-slick 3d engine.

For now we are limited to 1st Person Perspective used in games such as Quake or Unreal, but in future tuts we will extending the code to 3rd Person Views, such as Tomb Raider.

If you have any major problems or perhaps just need a helping hand to understanding something, then please use the Message Forum and I will get back to you ASAP.

It's probably a good idea to download the source code and play around with the EXE before you make a start, as it may help you to understand what is going on. As before I will only be explaining the new sections of code so it may help to have the source open as you work through this tut.

Download the Source here

Without any further ado, Lets build a 3d world and walk through it !!!!

#include <math.h>
#include <time.h>

Two more header files are needed for the maths functions (cos & sin) and randomize routines (srand).

#define num_of_objects 3
#define max_no_shape 1
#define max_no_tri 10
#define max_no_sides 10

I've used a couple of defined labels to store values that I can easily change, lets take a look at what each one does:

num_of_objects 3 I only wanted to have 3 objects in my world, you can increase this value to add more if you wish

max_no_shape 1 Used to define how many shapes we should have, this is only set as 1 which is the cube.

#define max_no_tri 10 This is the total amount of triangles of all shapes, remember we are using triangles and 2 Tri's = 1 Square Surface. (Yes, its a 5 sided cube as I didn't bother defining the underneath of the cube as it cant be seen) - hence 10 Tri's = 5 surfaces

#define max_no_sides 10 The amount of sides/polygons (triangles) each object is allowed to have, if we had a complicated object then the polygon count would be a lot higher. Quake III has a typical count of over 500+ for each character


const float piover180=0.0174532925f;
float dist;
float worldx,worldy,worldz;
float wrdtx,wrdty,wrdtz;
int num,num2,num3;

Mostly variables used by the engine to store temporary values, don't play with them!

struct {
float rotx,roty,rotz;
float tranx,trany,tranz;
} cam;

struct {
float vx[3+1],vy[3+1],vz[3+1];
} tri[max_no_tri+1];

The camera structure hasn't really changed from the previous tut, the TRI struct will hold all the co-ordinates in groups of 3 as we are dealing with triangles.

So to store just one triangle we would use:

tri[1].vx[1] = x point 1
tri[1].vy[1] = y point 1
tri[1].vz[1] = z point 1
tri[1].vx[2] = x point 2
tri[1].vy[2] = y point 2
tri[1].vz[2] = z point 2
tri[1].vx[3] = x point 3
tri[1].vy[3] = y point 3
tri[1].vz[3] = z point 3

struct {
int num_of_sides;
int side[max_no_sides];
int colour_of_side_r[max_no_sides];
int colour_of_side_g[max_no_sides];
int colour_of_side_b[max_no_sides];
} shape[max_no_shape+1];

The SHAPE structure is used to build the object, think of it as a link that holds the information for what vertex triangles are needed. I have included variables to hold the colour for each side (polygon that makes up the shape), although as you can see I have not included support for Texture Mapping - you should be able to include this quite easily. I will go into more detail about the information this structure holds later on.

struct {
float rotx,roty,rotz;
float tranx,trany,tranz;
float offsetx,offsety,offsetz;
float scalex,scaley,scalez;
int shape_id;
} object[num_of_objects+1];

The OBJECT structure holds the information for every object. In this example we have 3 cubes, because of the way this engine was put together you don't have to define a shape for every object. (what this means is although we have only defined 1 cube shape we can assign it to any number of objects - in this case 3 cubes)

cam.tranz=200;
cam.tranx=50;

Over in the INIT function we set the camera to the start position in the world. This will position it 50 units over to the right and back 200 units - which is hopefully just in front of our 3 cubes.

srand((unsigned)time(NULL));

// 5 Sided cube (no bottom)
tri[1].vx[1]=-2; tri[1].vy[1]=-2; tri[1].vz[1]=2;
tri[1].vx[2]=2; tri[1].vy[2]=-2; tri[1].vz[2]=2;
tri[1].vx[3]=-2; tri[1].vy[3]=2; tri[1].vz[3]=2;
tri[2].vx[1]=2; ...................
..........
shape[1].side[1]=1;
shape[1].side[2]=2;
shape[1].side[3]=3;
shape[1].side[4]=4;
shape[1].side[5]=5;
shape[1].side[6]=6;
shape[1].side[7]=7;
shape[1].side[8]=8;
shape[1].side[9]=9;
shape[1].side[10]=10;
shape[1].num_of_sides=10;

I'll be using the rand() function so we have to initialize it with the srand() function.

Next we define the co-ordinates for the square, I've already explained the format. (See above.)

Now to define the shape that makes up the object, really its just a case of putting all the triangles it needs into a shape. For example:

shape[1].side[1]=1; This means we want the shapes first side to use the coordinates stored in the first triangle (TRI structure) shape[1].side[2]=4; We want the second side of the shape to use the coordinates stored in the forth triangle

And so on, of course if you wanted to build a second object then you would use shape[WHATEVER].side[1]=??, as you can see the shape[1].num_of_sides variable is just holding the complete number of sides that make up this object (10).

At the moment every shape is limited to 10 sides, but this can be increased by adjusting the max_no_sides variable.

for (num2=1; num2<=10; num2=num2+2) {
shape[1].colour_of_side_r[num2]=rand()%255;
shape[1].colour_of_side_g[num2]=rand()%255;
shape[1].colour_of_side_b[num2]=rand()%255;
shape[1].colour_of_side_r[num2+1]=
shape[1].colour_of_side_r[num2];
shape[1].colour_of_side_g[num2+1]=
shape[1].colour_of_side_g[num2];
shape[1].colour_of_side_b[num2+1]=
shape[1].colour_of_side_b[num2];
}

Every side in the object needs a colour, this is done by setting 3 colour variables: shape[1].colour_of_side_r, shape[1].colour_of_side_g and shape[1].colour_of_side_b to a number between 0 and 255.

Of course the 'r' stands for red, 'g' for green and 'b' for blue, this is a different system to the colour commands we used earlier that expected a floating number between 0 and 1.0, it certainly makes life a lot easier. (and uses the same format as most popular paint packages)

Rather then define all 10 variables 3 times over (r,g,b) - I've used a loop that sets every second face a random colour. The idea being that as each surface of the cube is made of 2 triangles, I can set both sides of the cubes surface the same colour.

object[1].shape_id=1;
object[1].tranx=rand()%50;
object[1].tranz=rand()%100;

object[2].shape_id=1;
object[2].tranx=rand()%50;
object[2].tranz=rand()%100;

object[3].shape_id=1;
object[3].tranx=rand()%50;
object[3].tranz=rand()%100

The last bit of the setting up process involves initializing the objects, For this demo I only used 3 - but you could increase this amount by adjusting the num_of_objects variable.

Each object we want to use is defined by the shape, so the first thing we must do is set the variable object[1].shape_id to point to the shape we want to use (1). Each object has its own properties that we can control, these are:

Position object[WHATEVER].tranx
object[WHATEVER].trany
object[WHATEVER].tranz Where it will be placed in the 3d world
Offset object[WHATEVER].offsetx
object[WHATEVER].offsetx
object[WHATEVER].offsetx Do you want the rotation point to be moved from the middle of the object to a new position (NOT REALLY USED)
Rotation object[WHATEVER].rotx
object[WHATEVER].roty
object[WHATEVER].rotz Rotate the object around the X, Y and Z axis
Scale object[WHATEVER].scalex
object[WHATEVER].scaley
object[WHATEVER].scalez Scale the object (NOT SUPPORTED YET)

You can change any of these variables from within the program at anytime which will take immediate effect on the object. For example: object[2].rotx=5; will rotate object 2 around the X axis to 5 degrees.

In this program, I have setup 3 objects (pointing to shape 1) - at random X and Z positions.

Thankfully that's all the setting up out of the way!

calc_world();
for (num=1; num<=num_of_objects; num++) {
glPushMatrix();
glTranslatef(object[num].tranx,
object[num].trany,object[num].tranz);
glRotatef(object[num].rotx,1.0f,0.0f,0.0f);
glRotatef(object[num].roty,0.0f,1.0f,0.0f);
glRotatef(object[num].rotz,0.0f,0.0f,1.0f);
}

Down in the Display function I've added a new function calc_world(); that will sort out the positioning of the camera in relation to our little world. We will look at this in more detail later.

Next we setup a loop that will run through all the objects we have defined, the Matrix is temporary stored by the glPushMatrix() function as every object will have its own unique position in the world. Otherwise, like I've mentioned before every movement will have a knock-on effect for proceeding objects. (moving shape 1 by any amount will also move the shapes 2 and 3 as well)

num3=object[num].shape_id;

for (num2=1; num2<=shape[num3].num_of_sides; num2++) {

glBegin(GL_TRIANGLES);

glColor3ub(
shape[num3].colour_of_side_r[num2],
shape[num3].colour_of_side_g[num2],
shape[num3].colour_of_side_b[num2]);

glVertex3f(tri[shape[num3].side[num2]].vx[1],
tri[shape[num3].side[num2]].vy[1],
tri[shape[num3].side[num2]].vz[1]);

glVertex3f(tri[shape[num3].side[num2]].vx[2],
tri[shape[num3].side[num2]].vy[2],
tri[shape[num3].side[num2]].vz[2]);

glVertex3f(tri[shape[num3].side[num2]].vx[3],
tri[shape[num3].side[num2]].vy[3],
tri[shape[num3].side[num2]].vz[3]);

}

glEnd(); glPopMatrix();
}

Yet another loop is setup to loop through all the sides in the object. The glColor3ub function is just another variation of the glColour3f() command but accepts the values in a more standard decimal format. ( 0 - 255 ). After the shape has been displayed we end the shape with glEnd() and finally pop our Matrix back using the glPopMatrix() function, all ready for the next object.

// If the Left or Right cursor keys are pressed rotate
// the camera around the Y axis
if (keys[37]) cam.roty+=1.0f;
if (keys[39]) cam.roty-=1.0f;

// If the Q or A keys are pressed rotate the camera
// around the X axis, giving the impression of
// looking up and down
if (keys[65]) cam.rotx-=0.5f;
if (keys[81]) cam.rotx+=0.5f;

// If the Up or Down cursors are pressed move the
// camera into or out of the world by the specified
// amount in this case 4 units. (We
// will look at this later)
if (keys[38]) cam_move(-0.4);
if (keys[40]) cam_move(0.4);

// If the W or S keys are pressed move the camera
// along the Y axis. (Up and Down)
if (keys[83]) cam.trany-=0.05;
if (keys[87]) cam.trany+=0.05;

// If the < or > keys are pressed spin
// object 1 around the Y axis.
if (keys[188]){
object[1].roty+=1.5f;
if (object[1].roty>360) object[1].roty=0.0f; }
if (keys[190]){
object[1].roty-=1.5f;
if (object[1].roty<0) object[1].roty=360.0f; }

In the KEYBOARD function we can control how we can move about by using the various structures we used earlier.

void cam_move(float dist)
{
cam.tranx+=(float)sin(cam.roty*piover180)*dist;
cam.tranz+=(float)cos(cam.roty*piover180)*dist;
}

All the cam_move function does is to calculate a new X and Y position based on the old mathematicians favorites SIN and COSINE. I don't really want to go into detail about this but if you want the in`s and out`s then find an old math's book !

void calc_world(void)
{
wrdtx=-cam.tranx;
wrdtz=-cam.tranz;
wrdty=-cam.trany;

worldx=360-cam.rotx;
worldy=360-cam.roty;
worldz=360-cam.rotz;

glRotatef(worldx,1.0f,0,0);
glRotatef(worldy,0,1.0f,0);
glRotatef(worldz,0,0,1.0f);
glTranslatef(wrdtx,wrdty,wrdtz);
}

Lastly the calc_world function, This takes the new camera position away from the current world position and rotates it by the amount we specified in the cam.rot structures - giving the illusion of moving. This engine is limited to moving only in the Z axis - which is ideal for most 3d shoot'em games. If you want to program a Flight Sim, or something similar then you will need to pitch and roll which requires the 3d engine to move in all directions. I will post a new updated engine sometime in the future when I feel the need!

I would like to point out that the setupogl.h file needs the extra lines added:

void cam_move(float dist);
void calc_world(void);

Place these under the Init and Idle functions:

void Init(void);
void Idle(void);
void cam_move(float dist); <--
void calc_world(void); <--

Alternatively, use the new version of the setupogl.h file supplied with the source code.

And that's it ! - As I've already said it is fairly complicated, but stick with it, post any questions on the Message Forum and don't be afraid to ask for help. It's along way from being a good game engine and still lacks a lot of features such as support for texture mapping and moving objects - but that's for another tutorial!

It's taken me along time to prepare this tutorial, I just hope you enjoy it as much as I have writing it : )

Download a more Advanced Demo here

Have Fun!


PaulJG(Posted 2005) [#5]
Jungle.. just for you ;)

I'm really snowed under at work, and think I'm gonna be for a good few weeks yet. (havent touched Blitz for a few months !)

Anyway, just to finish up what I've started..

I'm posting the last few tutorials up - but please be aware they have not been ported over to Blitz just yet. They are still in C++ format, but you should be able to adapt them yourselves.

I will eventually get around to converting them - but in the meantine.. ENJOY !.