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 7 January 2012

2 Dimensional Matrix Rotation - Indexing Vertices


The previous post was all very well for taking a first step in matrices, but there were a couple of short comings, as follows;

First, it only rotated one single point (vertex). In a working program, we may want to rotate many vertices using the same rotation transformation that our matrix calculation provided. For example, a single object may be made up of several vertices, all at different displacements from the origin. Ideally, I want the transformation applied to all these points, to give the appearance of rotating the object.

Second, the coordinates of the vertex were permanently altered by the rotation transformation. This is not the ideal. I would want the “source object” to be static, with its coordinates fixed, feed those coordinates through the matrix calculation and apply the rotation, and finally output the modified vertices into a “destination object”, which would then subsequently be displayed.


I am going to modify that matrix_2d_handler() function so that it accepts a structure of source vertices in an array, modifies them, and copies the modified vertices to a similar, destination array structure. Naturally, it is going to be necessary to create this structure, and make sure that both the source and destination structures are made from the same struct type, and are of the same type. So, here's the structure...
  

struct vertices_2D

{

float x;

float y;

};

...here's the size I want that structure to be, plus how many coords per vertex...

 

const int NUM_VERTS = 17;

const int NUM_VERT_PTS = 2;

...and here is how I declare my two structures in main().

 

vertices_2D points[NUM_VERTS];

vertices_2D screen_points[NUM_VERTS];

Fairly straightforward. I feed those two structures into the modified matrix handler function, which is this...

 

void calc_screen_points(vertices_2D *_pts, vertices_2D *_scrn_pts, float _angle)

{

float Matrix_2D[2][2];

Matrix_2D[0][0] = cosf(_angle);

Matrix_2D[0][1] = -sinf(_angle);

Matrix_2D[1][0] = sinf(_angle);

Matrix_2D[1][1] = cosf(_angle);

int i;

for(i = 0; i < NUM_VERTS; i++)

{

_scrn_pts[i].x = (Matrix_2D[0][0] * _pts[i].x) + (Matrix_2D[0][1] * _pts[i].y);

_scrn_pts[i].y = (Matrix_2D[1][0] * _pts[i].x) + (Matrix_2D[1][1] * _pts[i].y);

}

}

...this way...

 

calc_screen_points(points, screen_points, deg_to_rad(angle));

Now the values in the array structure points never get altered, and the array structure screen_points are altered by angle. But there is one issue outstanding; populating the points array structure with the coordinates of the “shape”. Well, figure out the coordinates, and, remembering that for a 2D shape there are 2 points per vertex, make an array of the coordinates (I made this array global, in this case)...

 

float thrush_points[NUM_VERTS * NUM_VERT_PTS] =

{

-270.0f, 0.0f, // 0

-240.0f, -17.0f, // 1

-20.0f, -50.0f, // 2

18.0f, -90.0f, // 3

55.0f, -85.0f, // 4

85.0f, -50.0f, // 5

200.0f, -28.0f, // 6

225.0f, -95.0f, // 7

265.0f, -95.0f, // 8

275.0f, 10.0f, // 9

80.0f, 35.0f, // 10

-70.0f, 35.0f, // 11

-240.0f, 17.0f, // 12

35.0f, 35.0f, // 13

-70.0f, -5.0f, // 14

35.0f, 0.0f, // 15

-30.0f, -10.0f // 16

};

...and read these points into the array structure (I did it this way, this time around)...

 

void read_points(vertices_2D *_pts)

{

int i, j;

for(i = 0, j = 0; i < NUM_VERTS; i++, j = i * 2)

{

_pts[i].x = thrush_points[j];

_pts[i].y = thrush_points[j+1];

}

}

...called from within main() like this...

 

read_points(points);

Yes, it can be seen, I am not much of a fan of multi dimensional arrays, considering that in reality, they are just an abstraction, in any case.

Anyway, that's the core of it, but the shape is pretty uninteresting unless I connect the points with lines. Here's how I do that...

 

const int NUM_LINES = 18;

const int NUM_LINE_PTS = 2;

float line_order[NUM_LINES * NUM_LINE_PTS] =

{

0, 1,

1, 2,

2, 3,

3, 4,

4, 5,

5, 6,

6, 7,

7, 8,

8, 9,

9, 10,

10, 11,

11, 12,

12, 0,

1, 12,

11, 14,

14, 16,

16, 15,

15, 13

};

This is another globally made array, in this case. What is it doing? Each “pair” of numbers identifies two of the vertices from screen_points that need to be strung together with a line. So, to make the outline of my shape, vertex 0 will be connected to vertex 1 with a line, vertex 1 to 2, 2 to 3, and so on. Note, each vertex may easily be used more than once, to draw lines, so the number of lines may not coincide with the number of vertices available. That's okay.

To accept this data, I will make another structure...

  

struct screen_lines

{

int line_start;

int line_end;

};

...declare an instance of its type in main as...

 

screen_lines lines[NUM_LINES];

...read the data with this function...

 

void read_lines(screen_lines *_lines)

{

int i, j;

for(i = 0, j = 0; i < NUM_LINES; i++, j = i * 2)

{

_lines[i].line_start = line_order[j];

_lines[i].line_end = line_order[j+1];

}

}


// Called this way from inside main()

read_lines(lines);

The lines are finally drawn to the screen with this function...

 

void draw_lines(vertices_2D *_scrn_pts, screen_lines *_lines, float x_cor, float y_cor)

{

int i;

float x1, y1, x2, y2;

for(i = 0; i < NUM_LINES; i++)

{

x1 = _scrn_pts[_lines[i].line_start].x;

y1 = _scrn_pts[_lines[i].line_start].y;

x2 = _scrn_pts[_lines[i].line_end].x;

y2 = _scrn_pts[_lines[i].line_end].y;


al_draw_line(x1 + x_cor, y1 + y_cor, x2 + x_cor, y2 + y_cor, al_map_rgb(0,255,0), 0);

}

}

...which indexes the coordinates of the vertices to the lines array, and draws the corresponding line for each couple. The x_cor and y_cor parameters are just for centering the shape on the screen, placing the origin dead center, and not up in the left corner as it would be without them.

Here's a screen shot and the full, working code for Allegro 5.1, to see the results...


...and how all the above fits in;


#define _USE_MATH_DEFINES
#include
#include
#include

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

struct vertices_2D
{
float x;
float y;
};

struct screen_lines
{
int line_start;
int line_end;
};

const int NUM_VERTS = 17;
const int NUM_VERT_PTS = 2;
const int NUM_LINES = 18;
const int NUM_LINE_PTS = 2;

enum KB_KEYS {KEY_Q, KEY_W};

float thrush_points[NUM_VERTS * NUM_VERT_PTS] =
{
-270.0f, 0.0f, // 0
-240.0f, -17.0f, // 1
-20.0f, -50.0f, // 2
18.0f, -90.0f, // 3
55.0f, -85.0f, // 4
85.0f, -50.0f, // 5
200.0f, -28.0f, // 6
225.0f, -95.0f, // 7
265.0f, -95.0f, // 8
275.0f, 10.0f, // 9
80.0f, 35.0f, // 10
-70.0f, 35.0f, // 11
-240.0f, 17.0f, // 12
35.0f, 35.0f, // 13
-70.0f, -5.0f, // 14
35.0f, 0.0f, // 15
-30.0f, -10.0f // 16
};

float line_order[NUM_LINES * NUM_LINE_PTS] =
{
0, 1,
1, 2,
2, 3,
3, 4,
4, 5,
5, 6,
6, 7,
7, 8,
8, 9,
9, 10,
10, 11,
11, 12,
12, 0,
1, 12,
11, 14,
14, 16,
16, 15,
15, 13

};

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


//****************
void read_points(vertices_2D *_pts)
{
int i, j;
for(i = 0, j = 0; i < NUM_VERTS; i++, j = i * 2)
{
_pts[i].x = thrush_points[j];
_pts[i].y = thrush_points[j+1];
}
}

//****************
void read_lines(screen_lines *_lines)
{
int i, j;
for(i = 0, j = 0; i < NUM_LINES; i++, j = i * 2)
{
_lines[i].line_start = line_order[j];
_lines[i].line_end = line_order[j+1];
}
}

//****************
void calc_screen_points(vertices_2D *_pts, vertices_2D *_scrn_pts, float _angle)
{
float Matrix_2D[2][2];
Matrix_2D[0][0] = cosf(_angle);
Matrix_2D[0][1] = -sinf(_angle);
Matrix_2D[1][0] = sinf(_angle);
Matrix_2D[1][1] = cosf(_angle);

int i;
for(i = 0; i < NUM_VERTS; i++)
{
_scrn_pts[i].x = (Matrix_2D[0][0] * _pts[i].x) + (Matrix_2D[0][1] * _pts[i].y);
_scrn_pts[i].y = (Matrix_2D[1][0] * _pts[i].x) + (Matrix_2D[1][1] * _pts[i].y);
}
}

//****************
void draw_lines(vertices_2D *_scrn_pts, screen_lines *_lines, float x_cor, float y_cor)
{
int i;
float x1, y1, x2, y2;

for(i = 0; i < NUM_LINES; i++)
{
x1 = _scrn_pts[_lines[i].line_start].x;
y1 = _scrn_pts[_lines[i].line_start].y;
x2 = _scrn_pts[_lines[i].line_end].x;
y2 = _scrn_pts[_lines[i].line_end].y;

al_draw_line(x1 + x_cor, y1 + y_cor, x2 + x_cor, y2 + y_cor, al_map_rgb(0,255,0), 0);
}

}

//*******************
int main(int argc, char *argv[])
{
ALLEGRO_DISPLAY *display = NULL;
ALLEGRO_EVENT_QUEUE *event_queue = NULL;
ALLEGRO_TIMER *timer = NULL;

vertices_2D points[NUM_VERTS];
vertices_2D screen_points[NUM_VERTS];
screen_lines lines[NUM_LINES];

read_points(points);
read_lines(lines);

int SCREEN_X = 800;
int SCREEN_Y = 600;
int SC_X_CENT = SCREEN_X / 2;
int SC_Y_CENT = SCREEN_Y / 2;

float FPS = 60.0f;
float angle = 0.0f;

bool key[2] = {false, false};

bool draw = false;
bool loop = true;

al_init();
al_init_primitives_addon();
al_install_keyboard();

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_register_event_source(event_queue, al_get_keyboard_event_source());

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;

case ALLEGRO_EVENT_KEY_DOWN:
switch(event.keyboard.keycode)
{
case ALLEGRO_KEY_Q:
key[KEY_Q] = true;
break;

case ALLEGRO_KEY_W:
key[KEY_W] = true;
break;

default:
break;
}
break;

case ALLEGRO_EVENT_KEY_UP:
switch(event.keyboard.keycode)
{
case ALLEGRO_KEY_Q:
key[KEY_Q] = false;
break;

case ALLEGRO_KEY_W:
key[KEY_W] = false;
break;

default:
break;
}
break;

default:
break;
}

if(draw == true && al_event_queue_is_empty(event_queue))
{
draw = false;

if(key[KEY_Q])
{
angle -= 0.5f;
if(angle < 0.0f)
angle += 360;
}
if(key[KEY_W])
{
angle += 0.5f;
if(angle > 359.99f)
angle -= 360;
}

al_clear_to_color(al_map_rgb(0,0,0));
calc_screen_points(points, screen_points, deg_to_rad(angle));
draw_lines(screen_points, lines, SC_X_CENT, SC_Y_CENT);

al_flip_display();
}
}

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

return 0;
}



That's all for now, except never EVER draft your blogs in LibreOffice Writer again. 3D next time...


No comments:

Post a Comment