About this Blog

This is my first blog. Ever.

It is simply going to be about my hobby; playing with computer programming. I do not know much about blogging, but I will use this one to learn a bit more about it.

Programming has always been a bit of a passion for me, as from those early days when I first tapped in a sample BASIC program on my old Sinclair Spectrum back in 1986. I have been through many platforms, languages and OS's since, but always carried the hobby with me. I am not particularly good at it; perfection requires a large time investment and continuous practice. I do not have the luxury of the amount of time required to keep the fire burning constantly, so the hobby has inevitably gone through periods of extreme withering. I have, however, finally settled for C++, as the title of this blog implies, and play around with it for some entertainment when ever I can.

This here will serve me as a written record of what I am up to, and hopefully be a reinforcement to my memory every now and then. That is all there is to it.

So, if you read this blog, please don't expect anything snazzy, but be you welcome just the same!
Showing posts with label glEnableClientState. Show all posts
Showing posts with label glEnableClientState. Show all posts

Saturday, 31 March 2012

Allegro 5.1 / OpenGL Heightmap Loader

Back again after a break from coding, enforced by work (alas). This time I am here with some more OpenGL and something I always wanted to tackle; heightmaps. Again, I am using Allegro 5.1 as the API, and decided to use some of A5's features to accomplish the task. Two of the neat things about A5 for this purpose are the al_get_pixel() and al_unmap_rgb() functions, which allow you to get the pixel color and extract them into their rgb components, perfect for reading heightmaps. Also, to speed up the reading of a bitmap, pixel by pixel, there is the al_lock_bitmap() function, which turns the bitmap temporarily into a memory bitmap. It all helped. Now, I decided to use greyscale images, which each shade is represented by an equal value of the rgb components (eg; mid grey might be something like r = 128, g = 128, b = 128), so I would only need to use one of the components.

All that is very well, but I also had another objective; that my heightmap loader be able to read (and convert into terrain) any size of bitmap, so that I would not be restricted to reading only 100 x 100 or any other fixed size greyscale heightmap. To accomplish this, I decided to use STL vector, so that any size in total pixels could be stored in the vector, depending on the dimensions of the loaded heightmap bitmap. The basic idea is that each pixel will represent a point of terrain, with 3D Cartesian coordinates, where, the X and Y dimensions of the bitmap will be the respective OpenGL X and Z coordinates, when multiplied by a land_scale factor, and the color will be the elevation (OpenGL Y coordinate), the lighter the grey, the higher the elevation. Here's the heightmap I made for the experiment...


Originally, my intention was to use GL_TRIANGLE_STRIP to make the terrain, but around the web I found reports that it showed some (speed) inferiority when compared to using straight forward triangles with the now familiar glEnableClientState() function, so I dispensed with the idea of using GL_TRIANGLE_STRIP, and quite happily, too, as I was not too fond of it really.

So, that constitutes the preamble of the conditions I set myself for declaring mission accomplished. As per usual, the full code listing is posted at the end of the discussion. I will get to it, now.

The first task would be to load the bitmap, read the pixels, and create the 3D coordinates for each point. This is, really, the easy bit. Here's a code snippet...


vector<GLfloat> verts;

int BMP_WIDTH;
int BMP_HEIGHT;

ALLEGRO_BITMAP *ht_bmp = al_load_bitmap("landtile.jpg"); // load the heightmap.
ALLEGRO_COLOR ht_pixel;

int bmp_x = 0;
int bmp_z = 0; // the Y axis of the bmp, really, but considered "z" for conversion to OpenGL coords.

GLfloat land_scale = 5.0f;
GLfloat height_scale = 100.0f;
GLfloat height_true = 0.0f;

unsigned char r, g, b; // To store the retreived r,g,b components of each pixel.

// here I get the dimensions, in pixels, of the loaded heightmap.

BMP_WIDTH = al_get_bitmap_width(ht_map);
BMP_HEIGHT = al_get_bitmap_height(ht_bmp);

// In the full listing, there are a couple of "autocentering variables here, omitted in this snippet.
// Also, in the full listing, look out for the al_lock_bitmap() around here.

So, I now have my heightmap loaded and the program knows the bitmap's dimensions. Each pixel (point) needs to be "expanded" into 3 place holders for the x,y,z coords.

// Reserve the space in the vector, which is a good practice, with the total number of coordinate place holders...
verts.reserve(size_t(BMP_HEIGHT * BMP_WIDTH * 3)); // An x,y,z for each pixel read.

// ..and populate the verts vector...
for(bmp_z = 0; bmp_z < BMP_HEIGHT; bmp_z++)
{
    for(bmp_x = 0; bmp_x < BMP_WIDTH; bmp_x++)
    {
        verts.pushback(GLfloat(bmp_x * land_scale)); 

        // Get the "height" of the OpenGL Y coord according to the "color" of the pixel...
        ht_pixel = al_get_pixel(ht_bmp, bmp_x, bmp_z);
        al_unmap_rgb(ht_pixel, &r, &g, &b); // Thanks, Tomasu!

        height_true = GLfloat(float(r) / 255.0f) * height_scale; // Uses only the red component...
        verts.pushback(height_true); // Assign the value to the vector.

        verts.pushback(GLfloat(bmp_z * land_scale));
    }

}

al_destroy_bitmap(ht_bmp); // Once the values are stored in the vector, no further need for the bitmap.

So yeah, the height of the bitmap times the width times three will create the required number of places for the x, y, z for each pixel.
Now, to continue, I will be using the OpenGL glDrawElements() function. This, essentially, does the same thing that I did manually on this post. that is, connecting the points by indexing. Basically, it works this way;

glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, verts);
glDrawElements(GL_TRIANGLES, points_count, GL_UNSIGNED_INT, points);
glDisableClientState(GL_VERTEX_ARRAY);

Looks pretty simple. verts is the array that holds the vertices of each point to be connected, in sets of three (x,y,z). points is the order in which the vertices need to be connected to make the triangles. But hang on. I need to generate those connection points automatically. I will look at a simplified version of the problem. Here is an array of the points, considered a multidimesnional array, but really a contiguous vector.



25 points (0-24), in this case, gives me 16 squares into which I need to draw 32 triangles. First step, I need to reduce 25 to 16. So, BMP_WIDTH is 5 and BMP_HEIGHT is 5. I can get 16 very easily...

(BMP_WIDTH - 1) * (BMP_HEIGHT - 1) = 16



And I can draw the triangles very easily using the triangle pair count (which is the 16 that I got). First triangle...

vector<GLuint> points;

for(i = 0; i < triangle_count; i++)
{
    // Triangle 1
    points.push_back(i);
    points.push_back(i + 1);
    points.push_back(i + BMP_WIDTH);
    ....

And the second triangle...

    //Triangle 2
    points.push_back(i + 1);
    points.push_back(i + BMP_WIDTH);
    points.push_back(i + BMP_WIDTH + 1);
}

Going around counter clockwise. But there's a problem. Every time the calculation gets to the end of a row, it needs to skip a triangle pair in order to start drawing a new row, otherwise I get the last triangle of the first row warping across to the first triangle of the second row.


I need to have a dummy number to skip at the end of each row, so that the drawing of the triangles breaks. I modify the triangle count by the number of rows of squares, which is, indeed, the (BMP_HEIGHT - 1). So triangle count looks like this...

triangle_count = ((BMP_WIDTH - 1) * (BMP_HEIGHT - 1)) + (BMP_HEIGHT -1);

And implement a row skip counter (call it width_count). The function now looks like this...


int width_count = 0;
triangle_count = ((BMP_WIDTH - 1) * (BMP_HEIGHT - 1)) + (BMP_HEIGHT -1);

for(i = 0; i < triangle_count; i++)
{
    if(width_count == (BMP_WIDTH - 1)) // That is to say, it counts up to the end of the row...
    {
        continue; // Skip drawing that triangle pair...
        width_count = 0; // Reset width_count to 0.
    }
    else
    {
        // Triangle 1
        points.push_back(i);
        points.push_back(i + 1);
        points.push_back(i + BMP_WIDTH);

        //Triangle 2
        points.push_back(i + 1);
        points.push_back(i + BMP_WIDTH);
        points.push_back(i + BMP_WIDTH + 1);

        width_count++; //count each pair of triangles drawn.
    }
}



And that is, basically, how I did it. I suppose you could use a modulus of (BMP_WIDTH - 1) to accomplish the same thing, and omit the width_count, right?

Here's a screen shot and the complete listing on my pastebin account (easier than formatting it here...).


THE COMPLETE ALLEGRO 5.1 LISTING HERE!

And with a most basic color for height algorithm function added somewhere in there it is easy to make a pseudo scenery generator! Had some fun with this one.


That code not included here, maybe a refined version next time, so...

Until the next post; bye, for now!






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...