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...
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!
No comments:
Post a Comment