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!

Saturday 21 January 2012

3D object rotation

So, what was all that business about concatenating matrices in my previous post? Well, it is one of the vital steps in accomplishing the rotation of a 3D shape on a screen. Now that it is clear how to concatenate matrices, I am going to look at what actually goes into those matrices, and what they mean, in 3D graphics. Now, there are all sorts of discussions around the web concerning right hand and left hand coordinate systems. I am not bothered by them. Here's a link that delves a little into that subject, quite coherently. I am just going "right handed", and be done.

When dealing with this subject of 3D rotation, basically, there are these steps to keep in mind;

1. Make three (3x3) matrices, one for each axis, X, Y, and Z, and another one for the concatenation result. It is also a good idea to establish the angle variables for each matrix, too.

2. Make a function that calculates the operations in the matrices (basic trigonometry stuff).

3. Make a function that concatenates the three matrices into one.

4. Make a function that computes where the vertices of the shape are going to end up, applying the final, concatenated matrix.

5. Make a function that draws the shape to the screen.

Steps one and three have already been covered, in part, in my previous post. Step five has also previously been brushed upon in this other previous post. They will have their applicable review here, however.

I make a globally declared float matrix type, 3x3, this way;

typedef matrix[3][3];


...and define my matrices in main() this way;

// the matrices...
matrix X_Matrix, Y_Matrix, Z_Matrix, Final_Matrix;

// the angles for each of the three axis matrices...
float x_angle = 0.0f;
float y_angle = 0.0f;
float z_angle = 0.0f;


These will be in degrees, but C++ math.h trig functions use radians. I do like making my own little function for the conversion. Here it is (again)...

float deg_to_rad(float deg)
{
return deg * ((float)M_PI / 180.0f);
}


...and that is the end of Step One!

Now, for Step Two, here are the "formats" of my three matrices, X, Y and Z, respectively...



These are the operations that are going to be carried out in each matrix, with its respective angle fed to it. Here's the function that does it...

void Matrix_Handler(matrix X_Mat, matrix Y_Mat, matrix Z_Mat, float x_ang, float y_ang, float z_ang)
{
// X Axis Matrix;
X_Mat[0][0] = 1.0f;
X_Mat[0][1] = 0.0f;
X_Mat[0][2] = 0.0f;

X_Mat[1][0] = 0.0f;
X_Mat[1][1] = cosf(x_ang);
X_Mat[1][2] = sinf(x_ang);

X_Mat[2][0] = 0.0f;
X_Mat[2][1] = -sinf(x_ang);
X_Mat[2][2] = cosf(x_ang);

// Y Axis Matrix
Y_Mat[0][0] = cosf(y_ang);
Y_Mat[0][1] = 0.0f;
Y_Mat[0][2] = -sinf(y_ang);

Y_Mat[1][0] = 0.0f;
Y_Mat[1][1] = 1.0f;
Y_Mat[1][2] = 0.0f;

Y_Mat[2][0] = sinf(y_ang);
Y_Mat[2][1] = 0.0f;
Y_Mat[2][2] = cosf(y_ang);

// Z Axis Matrix
Z_Mat[0][0] = cosf(z_ang);
Z_Mat[0][1] = sinf(z_ang);
Z_Mat[0][2] = 0.0f;

Z_Mat[1][0] = -sinf(z_ang);
Z_Mat[1][1] = cosf(z_ang);
Z_Mat[1][2] = 0;

Z_Mat[2][0] = 0.0f;
Z_Mat[2][1] = 0.0f;
Z_Mat[2][2] = 1.0f;
}


...a bit of overkill, for clarity. It can be seen that, if I preset the matrix "cells" that do not contain any cosf(), sinf() or -sinf() operations at the beginning, this function could easily look like;

void Matrix_Handler(matrix X_Mat, matrix Y_Mat, matrix Z_Mat, float x_ang, float y_ang, float z_ang)
{
// X Axis Matrix;
X_Mat[1][1] = cosf(x_ang);
X_Mat[1][2] = sinf(x_ang);

X_Mat[2][1] = -sinf(x_ang);
X_Mat[2][2] = cosf(x_ang);

// Y Axis Matrix
Y_Mat[0][0] = cosf(y_ang);
Y_Mat[0][2] = -sinf(y_ang);

Y_Mat[2][0] = sinf(y_ang);
Y_Mat[2][2] = cosf(y_ang);

// Z Axis Matrix
Z_Mat[0][0] = cosf(z_ang);
Z_Mat[0][1] = sinf(z_ang);

Z_Mat[1][0] = -sinf(z_ang);
Z_Mat[1][1] = cosf(z_ang);
}


...as all the other cells are always going to be either a zero or a one, according to the format of the concerned matrix. But that is all aside the point. This is all there is, really, to Step Two, so I will press on.

Step Three is the multiplication of these matrices. Do I need to go into this again, seeing as it is already covered? Here's the functions, anyway...

void mult_matrix(matrix Fst_Mat, matrix Sec_Mat, matrix Result_Mat)
{
float temp = 0.0f;
int a, b, c;

for(a = 0; a < 3; a++)
{
for(b = 0; b < 3; b++)
{
for(c = 0; c < 3; c++)
{
temp += Fst_Mat[b][c] * Sec_Mat[c][a];
}
Result_Mat[b][a] = temp;
temp = 0.0f;
}
}
}

// *********************************

void Concatenate_Matrices(matrix First_Mat, matrix Second_Mat, matrix Third_Mat, matrix Fin_Mat)
{
matrix Concat_Mat;

mult_matrix(First_Mat, Second_Mat, Concat_Mat);
mult_matrix(Concat_Mat, Third_Mat, Fin_Mat);
}


The next Step, Four, is the first one that actually comes into contact with vertices or the "stored", 3D object. So, to be clear, here is the object. Yes, it is the everlasting cube. Look at the complete source code at the end of this post to see how it actually fits in.

// a structure to hold the coordinates of the vertex...
struct verts
{
float vx;
float vy;
float vz;
};

// *********************************

// a structure for the order of the drawing of the lines...
struct lines
{
int start;
int end;
};

//.....

// set the positions of the vertices (this is called from main(), with an array of type verts)...
void set_shape_verts(verts *_cube_verts)
{
_cube_verts[0].vx = -100.0f;
_cube_verts[0].vy = -100.0f;
_cube_verts[0].vz = 100.0f;

_cube_verts[1].vx = -100.0f;
_cube_verts[1].vy = -100.0f;
_cube_verts[1].vz = -100.0f;

_cube_verts[2].vx = 100.0f;
_cube_verts[2].vy = -100.0f;
_cube_verts[2].vz = -100.0f;

_cube_verts[3].vx = 100.0f;
_cube_verts[3].vy = -100.0f;
_cube_verts[3].vz = 100.0f;

_cube_verts[4].vx = -100.0f;
_cube_verts[4].vy = 100.0f;
_cube_verts[4].vz = 100.0f;

_cube_verts[5].vx = 100.0f;
_cube_verts[5].vy = 100.0f;
_cube_verts[5].vz = 100.0f;

_cube_verts[6].vx = 100.0f;
_cube_verts[6].vy = 100.0f;
_cube_verts[6].vz = -100.0f;

_cube_verts[7].vx = -100.0f;
_cube_verts[7].vy = 100.0f;
_cube_verts[7].vz = -100.0f;
}

// *********************************

// set which vertices are going to be connected by lines (this is called from main(), with an array of type lines)...
void set_shape_lines(lines *_cube_lines)
{
_cube_lines[0].start = 0;
_cube_lines[0].end = 1;

_cube_lines[1].start = 1;
_cube_lines[1].end = 2;

_cube_lines[2].start = 2;
_cube_lines[2].end = 3;

_cube_lines[3].start = 3;
_cube_lines[3].end = 0;

_cube_lines[4].start = 4;
_cube_lines[4].end = 5;

_cube_lines[5].start = 5;
_cube_lines[5].end = 6;

_cube_lines[6].start = 6;
_cube_lines[6].end = 7;

_cube_lines[7].start = 7;
_cube_lines[7].end = 4;

_cube_lines[8].start = 0;
_cube_lines[8].end = 4;

_cube_lines[9].start = 1;
_cube_lines[9].end = 7;

_cube_lines[10].start = 2;
_cube_lines[10].end = 6;

_cube_lines[11].start = 3;
_cube_lines[11].end = 5;
}


...and here is the function that sets the "rotated" position of those vertices.

void Calc_Screen_Verts(matrix Fin_Mat, ver ts *c_verts, verts *s_verts)
{
float new_x;
float new_y;
float new_z;

float x_center = (float)SCREEN_X / 2.0f;
float y_center = (float)SCREEN_Y / 2.0f;

int i;

for(i = 0; i < CUBE_VERTS; i++)
{
new_x = (Fin_Mat[0][0] * c_verts[i].vx) + (Fin_Mat[0][1] * c_verts[i].vy) + (Fin_Mat[0][2] * c_verts[i].vz);
new_y = (Fin_Mat[1][0] * c_verts[i].vx) + (Fin_Mat[1][1] * c_verts[i].vy) + (Fin_Mat[1][2] * c_verts[i].vz);
new_z = (Fin_Mat[2][0] * c_verts[i].vx) + (Fin_Mat[2][1] * c_verts[i].vy) + (Fin_Mat[2][2] * c_verts[i].vz);

s_verts[i].vx = new_x + x_center;
s_verts[i].vy = new_y + y_center;
s_verts[i].vz = new_z;
}
}


Note that another vert structure is passed to this function (s_verts). This is to receive the output of the rotated vertices held in c_verts. I do not actually want to alter the "mother" vertices of the cube, so I pass them through this function, modify (rotate) them using the concatenated Final_Matrix, and collect the rotation result in s_verts. There is no need to pass any angles, that has already been catered for by the matrix operation done previously. We need only pass the pre-rotated matrix to the function, and with the following operation, plot the new points positions...

Finally, Step Five uses a technique I already covered (again, here) to draw lines on the screen between the vertices held in the lines type structure array. Here it is...

void Draw_Shape(verts *s_verts, lines *c_lines)
{
int i;
for(i = 0; i < CUBE_LINES; i++)
{
al_draw_line(s_verts[c_lines[i].start].vx, s_verts[c_lines[i].start].vy,
s_verts[c_lines[i].end].vx, s_verts[c_lines[i].end].vy,
al_map_rgb(0,255,50), 0);
}
}


Obviously, I have missed out a great deal about perspective calculations, or vertex sorting for depth. That was not the point of these posts. All I wanted to do was review, quite superficially, the inner working of OpenGL. In any case, here is the complete working Allegro 5.1 source code for this demo.

#define _USE_MATH_DEFINES
#include "allegro5/allegro.h"
#include "allegro5/allegro_primitives.h"
#include "math.h"

/* `pkg-config --libs allegro-5.1 allegro_primitives-5.1` */

const int SCREEN_X = 800;
const int SCREEN_Y = 600;
const float FPS = 60.0f;
const int CUBE_LINES = 12;
const int CUBE_VERTS = 8;

// *********************************

typedef float matrix[3][3];

// *********************************

struct verts
{
float vx;
float vy;
float vz;
};

// *********************************

struct lines
{
int start;
int end;
};

// *********************************

float deg_to_rad(float deg)
{
return deg * ((float)M_PI / 180.0f);
}

// *********************************

void check_angles(float &x_ang, float &y_ang, float &z_ang)
{
if(x_ang >= 180.0f)
x_ang -= 360.0f;
else if(x_ang < -180.0f)
x_ang += 360.f;

if(y_ang >= 180.0f)
y_ang -= 360.0f;
else if(y_ang < -180.0f)
y_ang += 360.f;

if(z_ang >= 180.0f)
z_ang -= 360.0f;
else if(z_ang < -180.0f)
z_ang += 360.f;
}

// *********************************

void set_shape_verts(verts *_cube_verts)
{
_cube_verts[0].vx = -100.0f;
_cube_verts[0].vy = -100.0f;
_cube_verts[0].vz = 100.0f;

_cube_verts[1].vx = -100.0f;
_cube_verts[1].vy = -100.0f;
_cube_verts[1].vz = -100.0f;

_cube_verts[2].vx = 100.0f;
_cube_verts[2].vy = -100.0f;
_cube_verts[2].vz = -100.0f;

_cube_verts[3].vx = 100.0f;
_cube_verts[3].vy = -100.0f;
_cube_verts[3].vz = 100.0f;

_cube_verts[4].vx = -100.0f;
_cube_verts[4].vy = 100.0f;
_cube_verts[4].vz = 100.0f;

_cube_verts[5].vx = 100.0f;
_cube_verts[5].vy = 100.0f;
_cube_verts[5].vz = 100.0f;

_cube_verts[6].vx = 100.0f;
_cube_verts[6].vy = 100.0f;
_cube_verts[6].vz = -100.0f;

_cube_verts[7].vx = -100.0f;
_cube_verts[7].vy = 100.0f;
_cube_verts[7].vz = -100.0f;
}

// *********************************

void set_shape_lines(lines *_cube_lines)
{
_cube_lines[0].start = 0;
_cube_lines[0].end = 1;

_cube_lines[1].start = 1;
_cube_lines[1].end = 2;

_cube_lines[2].start = 2;
_cube_lines[2].end = 3;

_cube_lines[3].start = 3;
_cube_lines[3].end = 0;

_cube_lines[4].start = 4;
_cube_lines[4].end = 5;

_cube_lines[5].start = 5;
_cube_lines[5].end = 6;

_cube_lines[6].start = 6;
_cube_lines[6].end = 7;

_cube_lines[7].start = 7;
_cube_lines[7].end = 4;

_cube_lines[8].start = 0;
_cube_lines[8].end = 4;

_cube_lines[9].start = 1;
_cube_lines[9].end = 7;

_cube_lines[10].start = 2;
_cube_lines[10].end = 6;

_cube_lines[11].start = 3;
_cube_lines[11].end = 5;
}

// *********************************

void Matrix_Handler(matrix X_Mat, matrix Y_Mat, matrix Z_Mat, float x_ang, float y_ang, float z_ang)
{
// X Axis Matrix;
X_Mat[0][0] = 1.0f;
X_Mat[0][1] = 0.0f;
X_Mat[0][2] = 0.0f;

X_Mat[1][0] = 0.0f;
X_Mat[1][1] = cosf(x_ang);
X_Mat[1][2] = sinf(x_ang);

X_Mat[2][0] = 0.0f;
X_Mat[2][1] = -sinf(x_ang);
X_Mat[2][2] = cosf(x_ang);

// Y Axis Matrix
Y_Mat[0][0] = cosf(y_ang);
Y_Mat[0][1] = 0.0f;
Y_Mat[0][2] = -sinf(y_ang);

Y_Mat[1][0] = 0.0f;
Y_Mat[1][1] = 1.0f;
Y_Mat[1][2] = 0.0f;

Y_Mat[2][0] = sinf(y_ang);
Y_Mat[2][1] = 0.0f;
Y_Mat[2][2] = cosf(y_ang);

// Z Axis Matrix
Z_Mat[0][0] = cosf(z_ang);
Z_Mat[0][1] = sinf(z_ang);
Z_Mat[0][2] = 0.0f;

Z_Mat[1][0] = -sinf(z_ang);
Z_Mat[1][1] = cosf(z_ang);
Z_Mat[1][2] = 0;

Z_Mat[2][0] = 0.0f;
Z_Mat[2][1] = 0.0f;
Z_Mat[2][2] = 1.0f;
}

// *********************************

void mult_matrix(matrix Fst_Mat, matrix Sec_Mat, matrix Result_Mat)
{
float temp = 0.0f;
int a, b, c;

for(a = 0; a < 3; a++)
{
for(b = 0; b < 3; b++)
{
for(c = 0; c < 3; c++)
{
temp += Fst_Mat[b][c] * Sec_Mat[c][a];
}
Result_Mat[b][a] = temp;
temp = 0.0f;
}
}
}

// *********************************

void Concatenate_Matrices(matrix First_Mat, matrix Second_Mat, matrix Third_Mat, matrix Fin_Mat)
{
matrix Concat_Mat;

mult_matrix(First_Mat, Second_Mat, Concat_Mat);
mult_matrix(Concat_Mat, Third_Mat, Fin_Mat);
}

// *********************************

void Calc_Screen_Verts(matrix Fin_Mat, verts *c_verts, verts *s_verts)
{
float new_x;
float new_y;
float new_z;

float x_center = (float)SCREEN_X / 2.0f;
float y_center = (float)SCREEN_Y / 2.0f;

int i;

for(i = 0; i < CUBE_VERTS; i++)
{
new_x = (Fin_Mat[0][0] * c_verts[i].vx) + (Fin_Mat[0][1] * c_verts[i].vy) + (Fin_Mat[0][2] * c_verts[i].vz);
new_y = (Fin_Mat[1][0] * c_verts[i].vx) + (Fin_Mat[1][1] * c_verts[i].vy) + (Fin_Mat[1][2] * c_verts[i].vz);
new_z = (Fin_Mat[2][0] * c_verts[i].vx) + (Fin_Mat[2][1] * c_verts[i].vy) + (Fin_Mat[2][2] * c_verts[i].vz);

s_verts[i].vx = new_x + x_center;
s_verts[i].vy = new_y + y_center;
s_verts[i].vz = new_z;
}
}

// *********************************

void Draw_Shape(verts *s_verts, lines *c_lines)
{
int i;
for(i = 0; i < CUBE_LINES; i++)
{
al_draw_line(s_verts[c_lines[i].start].vx, s_verts[c_lines[i].start].vy,
s_verts[c_lines[i].end].vx, s_verts[c_lines[i].end].vy,
al_map_rgb(0,255,50), 0);
}
}

// *********************************

int main(int argc, char *argv[])
{
bool loop = true;
bool draw = false;
float x_angle = 0.0f;
float y_angle = 0.0f;
float z_angle = 0.0f;

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

matrix X_Matrix, Y_Matrix, Z_Matrix, Final_Matrix;

verts cube_verts[CUBE_VERTS];
set_shape_verts(cube_verts);

verts screen_verts[CUBE_VERTS];

lines cube_lines[CUBE_LINES];
set_shape_lines(cube_lines);

al_init();
al_init_primitives_addon();

al_set_new_display_option(ALLEGRO_VSYNC, 1, ALLEGRO_SUGGEST);
display = al_create_display(SCREEN_X, SCREEN_Y);
event_queue = al_create_event_queue();
timer = al_create_timer(1.0f / FPS);

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);

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 && al_event_queue_is_empty(event_queue))
{
draw = false;
al_clear_to_color(al_map_rgb(0,0,25));
y_angle += 0.50f;
x_angle += 0.25f;

check_angles(x_angle, y_angle, z_angle);

Matrix_Handler(X_Matrix, Y_Matrix, Z_Matrix, deg_to_rad(x_angle), deg_to_rad(y_angle), deg_to_rad(z_angle));
Concatenate_Matrices(X_Matrix, Y_Matrix, Z_Matrix, Final_Matrix);
Calc_Screen_Verts(Final_Matrix, cube_verts, screen_verts);
Draw_Shape(screen_verts, cube_lines);

al_flip_display();
}
}

al_stop_timer(timer);
al_flush_event_queue(event_queue);
al_destroy_event_queue(event_queue);
al_destroy_timer(timer);
al_destroy_display(display);

return 0;
}


...and a screen shot of what you should get...


That's it.

1 comment:

  1. Hello! I am currently going step by step through your tutorial, because I've got unconventional task from my teacher in school. Since there are many functions already built-in he gave me task to manualy build 3D rotating object via matrixes. I've done the steps but I can't find out how to change rotating only to x-axis not all ( y and z). If you can help me, it would be much appreciated :)

    ReplyDelete