Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » [A5] Trying to understand transformations

This thread is locked; no one can reply to it. rss feed Print
[A5] Trying to understand transformations
kenmasters1976
Member #8,794
July 2007

I'm trying to work with Allegro 5 transformations but I'm struggling to understand how they work. Maybe I'm just getting it all wrong so I wrote a couple code samples.

#SelectExpand
1#include <allegro5/allegro.h> 2#include <allegro5/allegro_primitives.h> 3 4ALLEGRO_DISPLAY *display = NULL; 5ALLEGRO_TRANSFORM t; 6 7int main() { 8 assert(al_init()); 9 assert(al_install_keyboard()); 10 11 uint32_t version = al_get_allegro_version(); 12 printf("Allegro version: %d.%d.%d\n", version >> 24, (version >> 16) & 255, (version >> 8) & 255); 13 display = al_create_display(800, 600); 14 assert(display); 15 assert(al_init_primitives_addon()); 16 17 al_clear_to_color(al_map_rgb(255, 255, 255)); 18 al_identity_transform(&t); 19 al_translate_transform(&t, 400, 400); 20 al_scale_transform(&t, .5, .5); 21 al_use_transform(&t); 22 al_draw_filled_rectangle(0, 0, 400, 400, al_map_rgb(255, 0, 0)); 23 al_scale_transform(&t, .5, .5); 24 al_use_transform(&t); 25 al_draw_filled_rectangle(0, 0, 400, 400, al_map_rgb(0, 255, 0)); 26 al_scale_transform(&t, .5, .5); 27 al_use_transform(&t); 28 al_draw_filled_rectangle(0, 0, 400, 400, al_map_rgb(0, 0, 255)); 29 al_translate_transform(&t, 400, 400); 30 al_flip_display(); 31 al_rest(5); 32}

For the previous code this is what I get vs. what I'd expect:
{"name":"611169","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/e\/d\/ede6f44fc3e93134343ee41009defbe9.png","w":1524,"h":378,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/e\/d\/ede6f44fc3e93134343ee41009defbe9"}611169

If instead I use the following code:

#SelectExpand
1 al_clear_to_color(al_map_rgb(255, 255, 255)); 2 al_identity_transform(&t); 3 al_translate_transform(&t, 400, 400); 4 al_scale_transform(&t, .5, .5); 5 al_use_transform(&t); 6 al_draw_filled_rectangle(0, 0, 400, 400, al_map_rgb(255, 0, 0)); 7 al_translate_transform(&t, 200, 200); 8 al_scale_transform(&t, .5, .5); 9 al_use_transform(&t); 10 al_draw_filled_rectangle(0, 0, 400, 400, al_map_rgb(0, 255, 0)); 11 al_translate_transform(&t, 200, 200); 12 al_scale_transform(&t, .5, .5); 13 al_use_transform(&t); 14 al_draw_filled_rectangle(0, 0, 400, 400, al_map_rgb(0, 0, 255)); 15 al_flip_display(); 16 al_rest(5);

{"name":"611170","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/9\/3\/934e0a438a9a35741a7fbb6c2e9c494a.png","w":1524,"h":378,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/9\/3\/934e0a438a9a35741a7fbb6c2e9c494a"}611170

So, what am I doing wrong here?.

Thanks.

Elias
Member #358
May 2000

Allegro's transformations work backwards compared to OpenGL, so:

al_translate_transform(&t, 400, 400);
al_scale_transform(&t, .5, .5);

Line 1 means take the passed transformation and move the result by 400/400. (The rectangle is drawn at 400/400, at full size.)

Line 2 means take the passed transformation and scale it by 0.5/0.5. (The rectangle is drawn at 200/200, at half size.)

al_scale_transform(&t, .5, .5);
al_translate_transform(&t, 400, 400);

Line 1 means take the passed transformation and scale it by 0.5/0.5. (The rectangle is drawn at 0/0, at half size.)

Line 2 means take the passed transformation and move the result by 400/400. (The rectangle is drawn at 400/400, at half size.)

Personally what I do is (from memory):

#SelectExpand
1al_identity_transform(&t); 2 3... 4 5al_identity_transform(&temp); 6al_translate_transform(&temp, 400, 400); 7al_compose_transform(&temp, t); 8t = temp; 9 10al_identity_transform(&temp); 11al_scale_transform(&temp, .5, .5); 12al_compose_transform(&temp, t); 13t = temp; 14 15...

That now should work as expected. Alternatively, when reading the transformations, just read them in the wrong order, starting at the last one instead of first one - and it should have the expected result!

--
"Either help out or stop whining" - Evert

kenmasters1976
Member #8,794
July 2007

Elias said:

Alternatively, when reading the transformations, just read them in the wrong order, starting at the last one instead of first one

But that's incredibly counter-intuitive, at least for me; even more so considering that the Allegro documentation says that transformations are applied in the order you write them. In your sample code, the way you have to use that al_identity_transform() + al_translate_transform() + al_compose_transform() combo every time just proves how counter-intuitive it is.

Consider the following shape:
611173

What should be a really simple shape to draw, requires the following code which, no matter how much I try to understand, I can't quite get what the transformations are doing:

    al_identity_transform(&t);
    al_scale_transform(&t, .5, .5);
    al_use_transform(&t);
    al_draw_filled_rectangle(0, 0, 400, 400, al_map_rgb(255, 0, 0));
    al_scale_transform(&t, .5, .5);
    al_translate_transform(&t, 100, 100);
    al_use_transform(&t);
    al_draw_filled_rectangle(-200, -200, 200, 200, al_map_rgb(0, 255, 0));
    al_scale_transform(&t, .5, .5);
al_translate_transform(&t, 50, 50);
al_use_transform(&t); al_draw_filled_rectangle(-200, -200, 200, 200, al_map_rgb(0, 0, 255));

The marked line, for example, what is it supposed to do? It's translating the transform by 50 units relative to what? I really don't understand how this works, I played with the values just to get the expected output which, by the way, makes this code only work at the corner of the screen, if I try to draw to the center of the screen, for example, then the values become totally different. Add translations, scale and rotations and the code becomes a real mess!. Can someone care to explain how the code above produces the result? How can you draw that same result at, say, the center of the screen?.

Maybe it's just me but I find the way transformations work to be incredibly counter-intuitive. Might as well prefer to instead use OpenGL matrices at this point.

Elias
Member #358
May 2000

I completely agree. The line you marked makes it so things will be drawn 50 pixel to the right and 50 pixel down compared to if you would not have that line (ignoring any transformations that came earlier). As I said, if you assume it is the first transformation instead of last things should make sense.

The only difference to OpenGL is that Allegro flips the order for the matrix multiplication (my version fixes that... I made my own translate/scale/rotate versions so my actual code is very short and intuitive).

--
"Either help out or stop whining" - Evert

Eric Johnson
Member #14,841
January 2013
avatar

Elias said:

Allegro's transformations work backwards compared to OpenGL

Just curious, what was the reasoning or train of thought that resulted in this?

kenmasters1976
Member #8,794
July 2007

Elias said:

The line you marked makes it so things will be drawn 50 pixel to the right and 50 pixel down compared to if you would not have that line

Yeah, but I'm drawing the rectangles using (-200, -200, 200, 200) which makes the value of 50 for that transformation to seem odd to me. I mean, whatever that set of transformations is doing to make the rectangles appear one within the other is still not clear to me.

Quote:

I made my own translate/scale/rotate versions so my actual code is very short and intuitive

Yeah, I guess you can enclose inside a function the combo of function calls you use but that means you're making a bunch of function calls behind the scenes. Still worth it to make your code easier to understand, though.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Allegro's transforms are pretty straight forward. That code you posted is misleading because it keeps applying progressive transforms instead of rebuild it anew for each shape.

The transformations are applied in the order that you apply them. Whether allegro is performing a left or right hand multiply behind the scenes doesn't matter. And yes it's backwards to OpenGL, but thank God for that, because IMO OpenGL is backwards.

EDIT
https://github.com/EdgarReynaldo/EagleGUI/blob/master/include/Eagle/Transforms.hpp

kenmasters1976
Member #8,794
July 2007

What would be the right way to draw that shape at the center of the screen, then? What if the blue and green rectangles' scale an position is relative to the rectangle that contains them? That's what the consecutive scale transforms are meant to represent and the kind of situation I have in my project and which lead to opening this thread in the first place. Rebuilding the transform from the start sounds like a waste since you'll have to apply the red and green scale anyway.

SiegeLord
Member #7,827
October 2006
avatar

Just curious, what was the reasoning or train of thought that resulted in this?

Because the way OpenGL does it is backwards. There's nothing complicated, or counter intuitive about the way Allegro does it as long as your mind hasn't been poisoned by OpenGL's matrix stack order.

What would be the right way to draw that shape at the center of the screen, then?

Your example is (unintentionally) contrived to favor OpenGL's order of multiplication, but in a realistic setting there is less difference between the two orders.

Here's a slightly more realistic use example (you can try to uncomment some lines to see how things compose):

#SelectExpand
1#include <allegro5/allegro.h> 2#include <allegro5/allegro_primitives.h> 3#include <stdio.h> 4#include <vector> 5 6struct Rectangle 7{ 8 std::vector<Rectangle*> children; 9 ALLEGRO_TRANSFORM transform; 10 ALLEGRO_COLOR color; 11 12 void draw(const ALLEGRO_TRANSFORM& parent_transform) const 13 { 14 ALLEGRO_TRANSFORM final_transform; 15 al_copy_transform(&final_transform, &transform); 16 al_compose_transform(&final_transform, &parent_transform); 17 al_use_transform(&final_transform); 18 19 al_draw_filled_rectangle(-200, -200, 200, 200, color); 20 21 for(const Rectangle* child : children) 22 { 23 child->draw(final_transform); 24 } 25 } 26}; 27 28 29int main(int argc, char **argv) 30{ 31 al_init(); 32 al_init_primitives_addon(); 33 34 ALLEGRO_DISPLAY* d = al_create_display(800, 600); 35 36 al_clear_to_color(al_map_rgb_f(0.0, 0.0, 0.0)); 37 38 float offx = 500; 39 float offy = 256; 40 41 42 Rectangle red; 43 Rectangle green; 44 Rectangle blue; 45 Rectangle pink; 46 47 red.color = al_map_rgb(255, 0, 0); 48 red.children.push_back(&green); 49 al_identity_transform(&red.transform); 50 al_translate_transform(&red.transform, offx, offy); 51 52 green.color = al_map_rgb(0, 255, 0); 53 green.children.push_back(&blue); 54 //green.children.push_back(&pink); 55 al_identity_transform(&green.transform); 56 al_scale_transform(&green.transform, 0.5, 0.5); 57 //al_rotate_transform(&green.transform, 3.1415/3); 58 //al_translate_transform(&green.transform, 100, 0); 59 60 blue.color = al_map_rgb(0, 0, 255); 61 al_identity_transform(&blue.transform); 62 al_scale_transform(&blue.transform, 0.5, 0.5); 63 64 pink.color = al_map_rgb(255, 0, 255); 65 al_identity_transform(&pink.transform); 66 // Since the rectangle is 400 pixels across, this will shift it to the left so it is next to the blue one. 67 al_translate_transform(&pink.transform, -400, 0); 68 al_scale_transform(&pink.transform, 0.5, 0.5); 69 70 ALLEGRO_TRANSFORM identity; 71 al_identity_transform(&identity); 72 red.draw(identity); 73 74 al_flip_display(); 75 76 al_rest(3.0); 77}

The OpenGL order version of the above code would not be that much different.

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

kenmasters1976
Member #8,794
July 2007

SiegeLord said:

There's nothing complicated, or counter intuitive about the way Allegro does it as long as your mind hasn't been poisoned by OpenGL's matrix stack order.

Guilty as charged, I guess.

This is how I'd draw the shape above using OpenGL and Allegro 5:

    al_clear_to_color(al_map_rgb(255, 255, 255));
    glTranslatef(SCR_WIDTH / 2, SCR_HEIGHT / 2, 0);
    glScalef(0.5, 0.5, 0);
    al_draw_filled_rectangle(-200, -200, 200, 200, al_map_rgb(255, 0, 0));
    glScalef(0.5, 0.5, 0);
    al_draw_filled_rectangle(-200, -200, 200, 200, al_map_rgb(0, 255, 0));
    glScalef(0.5, 0.5, 0);
    al_draw_filled_rectangle(-200, -200, 200, 200, al_map_rgb(0, 0, 255));

Which, in my opinion, is both easier to write and to understand but yes, I'm used to the way it works in OpenGL. Thankfully, Allegro 5 allows to mix these OpenGL calls with the Allegro code so I might stick to using this form for now.

Your code does indeed produce the correct output but I'm gonna have to study it a bit more before understanding it. Thanks for posting it, I will use it as a reference to try an understand the way Allegro transformations work so maybe, hopefully, I'll be comfortable using them.

Quote:

The OpenGL order version of the above code would not be that much different.

You may be right. Maybe once I understand Allegro transformations I might see they're not too different but right now I guess I'm in the OpenGL mindset. Your code will surely help me understand better. Thanks.

[EDIT:] Wait! Calling al_draw_bitmap() resets OpenGL transformations?. Well, I guess I'm back at square one.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

The correct transformation for each rectangle would be 'translate' (center on origin), 'scale' (and/or) 'rotate' , 'translate' (move to proper destination).

Or you could simply draw a rectangle centered on 0.0 yourself, that is 1.0 units wide, and 1.0 units high, scaled appropriately, and then centered on your destination.

The performance hit for rebuilding a matrix is pretty slim. Don't worry about it. And if you're worried about function calls, please don't.

void DrawMyRectangle(float width , float height , float cx , float cy , ALLEGRO_COLOR col) {
   ALLEGRO_TRANSFORM t;
   al_identity_transform(&t);
   al_scale_transform(&t , width , height);
   al_translate_transform(&t , cx , cy);
   al_use_transform(&t);
   al_draw_filled_rectangle(-0.5f , -0.5f , 0.5f , 0.5f , col);
}

/// later in main
DrawMyRectangle(400 , 400 , 200 , 200 , al_map_rgb(255,255,255));
DrawMyRectangle(200 , 200 , 200 , 200 , al_map_rgb(192,192,192));
DrawMyRectangle(100 , 100 , 200 , 200 , al_map_rgb(128,128,128));
DrawMyRectangle(50 , 50 , 200 , 200 , al_map_rgb(64,64,64));

{"name":"611175","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/d\/9\/d990e56a38578790702d0b238a8b9150.png","w":428,"h":452,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/d\/9\/d990e56a38578790702d0b238a8b9150"}611175

Calling al_draw_bitmap does not reset the transformations, but al_set_target_bitmap will and does.

kenmasters1976
Member #8,794
July 2007

Calling al_draw_bitmap does not reset the transformations, but al_set_target_bitmap will and does.

The following code draws two rectangles centered on the Allegro window, as expected, but the bitmap is drawn at the top-left corner of the window.

#SelectExpand
1#include <allegro5/allegro.h> 2#include <allegro5/allegro_image.h> 3#include <allegro5/allegro_opengl.h> 4#include <allegro5/allegro_primitives.h> 5 6#define SCR_WIDTH 800 7#define SCR_HEIGHT 600 8 9ALLEGRO_DISPLAY *display = NULL; 10ALLEGRO_BITMAP *bmp; 11 12int main() { 13 assert(al_init()); 14 assert(al_install_keyboard()); 15 16 uint32_t version = al_get_allegro_version(); 17 printf("Allegro version: %d.%d.%d\n", version >> 24, (version >> 16) & 255, (version >> 8) & 255); 18 display = al_create_display(SCR_WIDTH, SCR_HEIGHT); 19 assert(display); 20 assert(al_init_primitives_addon()); 21 assert(al_init_image_addon()); 22 23 bmp = al_load_bitmap("mysha.pcx"); 24 al_clear_to_color(al_map_rgb(255, 255, 255));
25 glTranslatef(SCR_WIDTH / 2, SCR_HEIGHT / 2, 0);
26 al_draw_filled_rectangle(-100, -100, 100, 100, al_map_rgb(255, 0, 0));
27 al_draw_filled_rectangle(-10, -10, 10, 10, al_map_rgb(255, 255, 0));
28 al_draw_bitmap(bmp, 0, 0, 0);
29 al_flip_display(); 30 al_rest(2); 31}

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Draws an unscaled, unrotated bitmap at the given position to the current target bitmap (see al_set_target_bitmap).

I don't know if al_draw_bitmap_region respects the current transformations or not.

EDIT
I know the primitives addon respects the transformations. You could render your bitmap as a primitive triangle fan.

kenmasters1976
Member #8,794
July 2007

I'm pretty sure the manual description for al_draw_bitmap() means unscaled, unrotated just as in opposed to the al_draw_scaled_bitmap() and al_draw_rotated_bitmap() versions but it's still meant to respect the current transformations and it does, at least it does if you use the Allegro transformation functions but apparently not if you use the OpenGL transformations so, in this case, using the primitives addon seems indeed like an alternative.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Thing is, every time you change the target bitmap, the view and projection matrices get reset.

If you want to do it with OpenGL alone, you have to do it this way :

void ResetTransforms() {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0 , SCREEN_WIDTH , 0 , SCREEN_HEIGHT , -1.0 , 1.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

This sets you up with a plain orthographic 2D projection matrix and an identity view matrix.

Call this after setting the target bitmap. If you're using a buffer, use its width and height for SCREEN_WIDTH and SCREEN_HEIGHT.

Then make your OpenGL calls and you can draw directly on the allegro screen.

Anytime you want to restore your projections, reset the target bitmap (hope they don't cache the previous target and NOP if its the same).

Personally I think allegro's transforms are easier.

EDIT
I checked the allegro source, and al_draw_bitmap uses its own transformations, along with every other bitmap drawing routine, because they all use the same underlying function but it doesn't respect OpenGL state, only the current allegro transform for the current target bitmap

Elias
Member #358
May 2000

But when the bitmap is drawn the glTranslate() would still be active (al_draw_bitmap doesn't have to know about it), so it should work.

My guess is that al_draw_bitmap does something like temporarily changing the global transformation, which would reset any OpenGL transformation you have set.

--
"Either help out or stop whining" - Evert

kenmasters1976
Member #8,794
July 2007

Edgar, I don't want to reset the transformations, on the contrary, I'd expect for al_draw_bitmap() not to.

Elias said:

But when the bitmap is drawn the glTranslate() would still be active (al_draw_bitmap doesn't have to know about it), so it should work.

Yeah, exactly my thought but apparently it's not so in practice.
Can't check the Allegro source right now but if, as Edgar points out in his edit, al_draw_bitmap() (and all other bitmap drawing routines) work based on Allegro transformations then if the first thing these functions do is, for example, call al_get_current_transform() then the result might not reflect any changes you manually made using OpenGL function calls.

So, I guess I have to understand Allegro transformations or attempt to draw the bitmaps using primitives, as suggested earlier.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Nobuddy listens to me. :'(

https://github.com/liballeg/allegro5/blob/master/src/bitmap_draw.c#L111-L119

It doesn't matter what you set with OpenGL, because allegro backs up and changes and then restores the current transformation in order to perform its duty as a function which is able to h or v flip as well as scale rotate and move an image.

Just use allegro's transforms or else get the gl texture id from allegro and draw it yourself with GL_QUAD or GL_TRIANGLE_FAN. You can't have it both ways here my friend.

kenmasters1976
Member #8,794
July 2007

I did listen to you, I was just poiting out what I originally expected.

Just use allegro's transforms or else get the gl texture id from allegro and draw it yourself with GL_QUAD or GL_TRIANGLE_FAN. You can't have it both ways here my friend.

As mentioned in the last line of my previous post, that's what I will do.

Thanks everyone for your help in this thread. It was really useful.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

kenmasters1976
Member #8,794
July 2007

Yeah, after all the explanations in this thread and studying the contributed code I was able to make the Allegro transforms work as expected in my project. I still don't find them quite as intuitive but I guess I'm starting to get the hold of it.

Thanks again and I will take a look at those links.

Go to: