Wednesday 4 January 2012

OpenGL / Allegro 5.1 Vertex Array with texture coords

After spending some time making my own procedure to interpret arrays of vertices, to simplify making slightly more complex shapes other than cubes, and passing them to glVertex3f() sequentially, I suddenly came across - in the GL docs - a neat feature of OpenGL which had already considered this possibility;

glEnableClientState()

All the various options that it can handle are listed there, but what interested me most at this stage was passing the vertex and texture coordinates to make the Python space craft from the old space trader game, Elite, by David Braben,(a classic I used to play on the Spectrum: could it be at all possible that anyone has not heard of it?).

So, with that, I will bypass my own functions for handling arrays (which I WAS going to post here) and stick to OpenGL's functions, as that is what I am learning. First, I (hand) drew the python in plan and side elevation on a piece of squared paper, and figured out the various points in X-Y-Z coordinates. These are in the array GLfloat python_verts[] in the code posted below. Fairly simple, that part. Here are the basics of how to implement that into code;

// tell GL what you are planning to do with the array you are going to pass it...
glEnableClientState(GL_VERTEX_ARRAY);

// create a pointer to the array with the following GL function...
glVertexPointer(3, GL_FLOAT, 0, python_verts);


Basically, that pointer is causing OpenGL to select the correct glVertex..() to interpret your array. In this case glVertex3f(), because we specify that the vertices are in sets of three, and are GLfloat type, and that they are listed in the python_verts array.

Now, I made my python with a series of triangles (respecting the outward faces vertex order being counter clockwise, of cour se!), so now to draw the Python;

glDrawArrays(GL_TRIANGLES, 0, 54);

Where did the 54 come from? The Python is made up of 18 triangles, each of 3 vertices, so it has to iterate 54 time to draw each individual triangle. Finally, disable the set Clientstate...

glDisableClientState(GL_VERTEX_ARRAY);


Everything made easy. It even automatically invokes glBegin() and glEnd(), so there is no need to call this specifically to draw the triangles...

Then (with Pinta) I drew the experimental texture I wanted to try out mapped onto the Python. I made a 256 x 256 texture so that I can later experiment a bit more with mip-mapping. Here 'tis...




Now, to map this, a couple of additional topics need to be covered. First, one must be very methodical (and patient) while doing this.

Next, remember that the coordinates of (2D) textures are mapped this way...
...with the Y axis 0 being at the bottom, and not the top like most paint programs have it. The easiest approach to overcome this issue is probably to temporarily flip the texture vertically while you plot the positions on the texture, and once you have the coordinates, flip it back again the way it was. Why do I say this? You are going to be working with some numbers to get the pixel positions in coefficients of the image size. I will do an example of texture mapping on one face to clarify. Here's the flipped image...



I am going to texture map the very first triangle drawn, which is the upper left nose of the Python. In the picture above, it is the first triangle encountered as from the lower left of the graphic.

But before I start mapping, i am going to look at the order of the drawing of the vertices for that triangle...
0.0f,0.0f,-9.0f, -3.0f,0.0f,0.0f, 0.0f,1.5f,-2.0f,

It goes from the nose, to the "wing-tip", to the center line on the front cabin. That is the same order I want to map the texture with. Bearing this in mind, I will make a texture coordinate for each of those points, in the format required by glTexCoord2f().

Starting at the nose of the Python, in the flipped texture graphic, I obtain the texture coordinates by dividing the actual pixel position of the required point (in X, Y) order by the total corresponding size of the image. For the very nose tip;

X = 63 / 256 = 0.246
Y = 256 / 256 = 1.000


Write that down like this...
0.246f, 1.000f

Now the wing-tip, keeping in with following the order of the drawn vertices...

X = 0 / 256 = 0.000
Y = 102 / 256 = 0.398

And the last center line point...

X = 63 / 256 = 0.246
Y = 140 / 256 = 0.547

So, here are the UV coordinates for the "section" of the texture to be mapped to the upper left nose of the python...

0.246f, 1.000f, 0.000f, 0.398f, 0.246f, 0.547f


...and that needs to be done for each and ev ery vertex of the whole object, with its corresponding bit of the texture. Once all the points are mapped, put them into another array, for example python_tex_coords[]. Don't forget to Y axis flip the texture back to how it was, either. And now take a break after doing it or you'll go barmy.

Finally, to activate the texture mapping in OpenGL, enable another Client State (along with the previously demonstrated one) and make a pointer to the texture coordinates. Here's how...

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

glVertexPointer(3, GL_FLOAT, 0, python_verts);
glTexCoordPointer(2, GL_FLOAT, 0, python_tex_coords);

glDrawArrays(GL_TRIANGLES, 0, 54);

glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);

Notice only that the first parameter of glTexCoordPointer() is 2. this is because there are only two uv coords for every vertex point (which are, themselves, three). It works the same as glVertexPointer, in all other respects.

Here's a screen shot (Blue Danube, anyone?)...

And that is it. I deliberately left some commented out things I was playing with in the code posted below, for reference, as there are more ways to draw from arrays that OpenGL, in its wisdom, can do.

#include "allegro5/allegro.h"
#include "allegro5/allegro_opengl.h"
#include "allegro5/allegro_image.h"
#include "GL/glu.h"

int SCREEN_X = 800;
int SCREEN_Y = 600;

GLfloat python_verts[] =
{
// Nose
// upper left
0.0f,0.0f,-9.0f, -3.0f,0.0f,0.0f, 0.0f,1.5f,-2.0f,
//upper right
0.0f,0.0f,-9.0f, 0.0f,1.5f,-2.0f, 3.0f,0.0f,0.0f,
// lower left
0.0f,0.0f,-9.0f, 0.0f,-1.5f,-2.0f, -3.0f,0.0f,0.0f,
// lower right
0.0f,0.0f,-9.0f, 3.0f,0.0f,0.0f, 0.0f,-1.5f,-2.0f,
// Cabin
// upper left
-3.0f,0.0f,0.0f, 0.0f,1.5f,2.0f, 0.0f,1.5f,-2.0f,
// upper right
3.0f,0.0f,0.0f, 0.0f,1.5f,-2.0f, 0.0f,1.5f,2.0f,
// lower left
-3.0f,0.0f,0.0f, 0.0f,-1.5f,-2.0f, 0.0f,-1.5f,2.0f,
// lower right
3.0f,0.0f,0.0f, 0.0f,-1.5f,2.0f, 0.0f,-1.5f,-2.0f,
// Tail forward
// upper left
-3.0f,0.0f,0.0f, -2.0f,0.0f,4.0f, 0.0f,1.5f,2.0f,
// upper right
3.0f,0.0f,0.0f, 0.0f,1.5f,2.0f, 2.0f,0.0f,4.0f,
// lower left
-3.0f,0.0f,0.0f, 0.0f,-1.5f,2.0f, -2.0f,0.0f,4.0f,
//lower right
3.0f,0.0f,0.0f, 2.0f,0.0f,4.0f, 0.0f,-1.5f,2.0f,
// Tail rear
// upper left
-2.0f,0.0f,4.0f, 0.0f,0.8f,4.0f, 0.0f,1.5f,2.0f,
// upper right
0.0f,1.5f,2.0f, 0.0f,0.8f,4.0f, 2.0f,0.0f,4.0f,
// lower left
-2.0f,0.0f,4.0f, 0.0f,-1.5f,2.0f, 0.0f,-0.8f,4.0f,
// lower right
0.0f,-1.5f,2.0f, 2.0f,0.0f,4.0f, 0.0f,-0.8f,4.0f,
// stern
// left
-2.0f,0.0f,4.0f, 0.0f,-0.8f,4.0f, 0.0f,0.8f,4.0f,
//right
2.0f,0.0f,4.0f, 0.0f,0.8f,4.0f, 0.0f,-0.8f,4.0f
};

GLfloat python_tex_coords[] =
{
// Nose
// upper left
0.246f,1.000f, 0.000f,0.398f, 0.246f,0.547f,
// upper right
0.246f,1.000f, 0.246f,0.547f, 0.488f,0.398f,
// lower left
0.746f,1.000f, 0.746f,0.547f, 1.000f,0.398f,
// lower right
0.746f,1.000f, 0.492,0.398f, 0.746f,0.547f,
// Cabin
// upper left
0.000f,0.398f, 0.246f,0.242f, 0.246f,0.547f,
// upper right
0.488f,0.398f, 0.246f,0.547f, 0.246f,0.242f,
// lower left
1.000f,0.398f, 0.746f,0.547f, 0.746f,0.242f,
// lower right
0.492f,0.398f, 0.746f,0.242f, 0.746f,0.547f,
// Tail forward
// upper left
0.000f,0.398f, 0.055f,0.145f, 0.246f,0.242f,
// upper right
0.488f,0.398f, 0.246f,0.242f, 0.429f,0.145f,
// lower left
1.000f,0.398f, 0.746f,0.242f, 0.945f,0.145f,
// lower right
0.492f,0.398f, 0.559f,0.145f, 0.746f,0.242f,
// Tail rear
// upper left
0.055f,0.145f, 0.246f,0.140f, 0.246f,0.242f,
// upper right
0.246f,0.242f, 0.246f,0.140f, 0.429f,0.145f,
// lower left
0.945f,0.145f, 0.746f,0.242f, 0.746f,0.145f,
// lower right
0.746f,0.242f, 0.559f,0.145f, 0.746f,0.145f,
// Stern
// left
0.062f,0.074f, 0.246f,0.000f, 0.246f,0.140f,
// right
0.418f,0.074f, 0.246f,0.140f, 0.246f,0.000f
};


void camera_3D_setup()
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(35.0, (GLdouble)SCREEN_X / (GLdouble)SCREEN_Y, 1.0, 100.0);
}

void draw_python(GLuint ogl_tex, GLfloat x_ang, GLfloat y_ang, GLfloat z_ang)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
al_clear_to_color(al_map_rgb(0,0,30));

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

glTranslatef(0.0f, 0.0f, -30.0f);
glRotatef(x_ang, 1.0f, 0.0f, 0.0f);
glRotatef(y_ang, 0.0f, 1.0f, 0.0f);
glRotatef(z_ang, 0.0f, 0.0f, 1.0f);

glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);

glBindTexture(GL_TEXTURE_2D, ogl_tex);

// No need to do glBegin() or glEnd() when using these GL functions
// They appear to intrinsic to the functions, like glDrawElements
// In a similar fashion to gluQuadricObjects, like gluSphere().
glEnableClientState(GL_VERTEX_ARRAY);
//glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

glVertexPointer(3, GL_FLOAT, 0, python_verts);
glTexCoordPointer(2, GL_FLOAT, 0, python_tex_coords);
//glColorPointer(3, GL_FLOAT, 0, python_colors);

//glDrawElements(GL_TRIANGLES, 54, GL_UNSIGNED_INT, python_vert_index);
glDrawArrays(GL_TRIANGLES, 0, 54);

glDisableClientState(GL_TEXTURE_COORD_ARRAY);
//glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);

glDisable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);

glFlush();
}

int main(int argc, char *argv[])
{
float FPS = 60.0f;
bool loop = true;
bool draw = false;

GLfloat x_angle = 70.0f;
GLfloat y_angle = 0.0f;
GLfloat z_angle = 180.0f;
GLuint python_tex;

ALLEGRO_DISPLAY *display = NULL;
ALLEGRO_TIMER *timer = NULL;
ALLEGRO_EVENT_QUEUE *event_queue = NULL;
ALLEGRO_BITMAP *python_bmp = NULL;

al_init();
al_init_image_addon();

al_set_new_display_flags(ALLEGRO_OPENGL);
display = al_create_display(SCREEN_X, SCREEN_Y);
timer = al_create_timer(1.0f / FPS);
event_queue = al_create_event_queue();

python_bmp = al_load_bitmap("pythontexture.jpg");
python_tex = al_get_opengl_texture(python_bmp);

al_register_event_source(event_queue, al_get_display_event_source(display));
al_register_event_source(event_queue, al_get_timer_event_source(timer));

al_start_timer(timer);

camera_3D_setup();

while(loop)
{
ALLEGRO_EVENT event;
al_wait_for_event(event_queue, &event);

switch(event.type)
{
case ALLEGRO_EVENT_DISPLAY_CLOSE:
loop = false;
break;

case ALLEGRO_EVENT_TIMER:
draw = true;
break;

default:
break;
}

if(draw == true && al_event_queue_is_empty(event_queue))
{
draw = false;
x_angle += 0.25f;
//y_angle -= 0.35f;
z_angle += 0.5f;
draw_python(python_tex, x_angle, y_angle, z_angle);
al_flip_display();
}
}


return 0;
}

Peaceful, non-violent, involving and enthralling subjugation and distraction for the masses, if only they will have the nerve to try it...

No comments:

Post a Comment