Gameboy Advance Rotate and Scale Inner workings
Having read the PERN Project Day Three tutorial I was determined to spend a day working out
exactly (as exactly as I could) what the Gameboy Advance was doing.
anyone who has done nearest neighbour rotate,scaling or shearing you will recognise this equation:
which expands to
tile_x = (a * screen_x) + (b * screen_y)
tile_y = (c * screen_x) + (d * screen_y)
see matrix maths for a little more info
What this means:
Basically the gameboy will calculate which pixel to display on a screen pixel by pixel by doing the above sum for
each pixel it has to display within the sprite bounding rect.
N.B. the origins are moved from the top left corner to the center so;
screen_x is (bounding_box_x - physical_pixel_x)
and
physical_tile_x is tile_x + (tile_width / 2)
and similar for the Y coordinate
N.B. Unlike mathmatics where the y axis runs up the page i.e. origin at the bottom left, the game boy has the y axis running down the page;
Rotation
from the Pern Project Tutorial you will know (if you've read it) that the rotation matrix is
for rotation by r clockwise.
| cos(r) | sin(r) | | -sin(r) | cos(r) |
| * | | = | |
The maths ... Remeber Y axis is inverted +ve down NOT up!
consider tile point (x,y) will rotate to screen point (x', y')
a line from tile point (x, y) to (0,0) is at angle T with length L
because we are not scaleing
a line from screen point (x', y') to (0,0) is at angle S and the same length L
from using the Sin/Cos definitions we get following equations
sin(T)=y/L; cos(T)=x/L;
sin(S)=y'/L; cos(S)=x'/L;
rearange and we get
y = L.Sin(T); x = L.Cos(T);
y' = L.Sin(S); x' = L.Cos(S);
we are interested in how to get from (x', y') to (x,y)
T the tile angle is S (the screen points angle) minus the rotation amount R
(again I will remind you that the y axis is upside down!)
i.e. T = S - R; so
y = L.Sin( S - R ); x = L.Cos( S - R);
using the difference formulas
Sin( A - B ) = Sin(A).Cos(B) - Cos(A).Sin(B);
Cos( A - B ) = Cos(A).Cos(B) + Sin(A).Sin(B);
( and of cource A.(B+C) = A.B + A.C !)
we get
x = L.Cos(S).Cos(R) + L.Sin(S).Sin(R);
y = L.Sin(S).Cos(R) - L.Cos(S).Sin(R);
we can change some of there into y' and x'
x = x'.Cos(R) + y'.Sin(R);
y = y'.Cos(R) - x'.Sin(R);
which can be shuffled into
tile_x = (a * screen_x) + (b * screen_y)
tile_y = (c * screen_x) + (d * screen_y)
i.e.
tile_x = (Cos(R) * screen_x) + (Sin(R) * screen_y)
tile_y = (-Sin(R) * screen_x) + (Cos(R) * screen_y)
and from the matrix maths we can calculate the deteminant
det = (Cos(R) * Cos(R)) - (Sin(R) * -Sin(R));
which is (Cos(R)^2) + (Sin(R)^2)
which and good maths book will tell you is 1
thus the rotation matrix will not change the area of the shape
Scaling
O.K. so you want to scale your sprite too.
will for that you want
screen_x = x_zoom * tile_x
screen_y = y_zoom * tile_x
lets return to the calcuating equation
tile_x = (a * screen_x) + (b * screen_y)
tile_y = (c * screen_x) + (d * screen_y)
rearanging the required equation give
tile_x = ((1/zoomx) * screen_x) + (0 * screen_y)
tile_y = (0 * screen_x) + (1/zoomy * screen_y)
obviously setting zoomx==zoomy you will get zooming
and as you see setting zoom to a number greater than 1 reduces the size
and setting a number less than 1 but greater than 0 increases the size
and from the matrix maths
the area change (determinant) is
(1/zoomx * 1/zoomy)
setting zoomx == zoomy == 0.5
will result in the area increasing by 4
(both sides increase by 2)
I want both at once
O.K. that's fine, either re work the initial rotation equations
using the following
y' = SFy.L.Sin(S); x' = SFx.L.Cos(S);
or, and a much better way is to multiply the two matricies
see the matrix maths for more info
Cos(R) Sin(R) (1/zoomx) 0
-Sin(R) Cos(R) * 0 (1/zoomy)
=
(Cos(R)/zoomx) (Sin(R)/zoomy)
(-Sin(R)/zoomx) (Cos(R)/zoomy)
N.B due to the asymetric nature of there two matricies
(1/zoomx) 0 Cos(R) Sin(R)
0 (1/zoomy) * -Sin(R) Cos(R)
=
(Cos(R)/zoomx) (Sin(R)/zoomx)
(-Sin(R)/zoomy) (Cos(R)/zoomy)
So what does this mean
well its simple (ish)
Scale * Rotate means Scale then Rotate
Rotate * Scale means Rotate then Scale
but as you see if (zoomx == zoomy) then it does not matter
Sheering
for sheering a shape us the following:
I'm using the same notation as I did on the rotate equations
(shearing in x)
y' = y;
x' = x - Sf.y;
rearange :
y = y';
x = x' +Sf.y;
substute: (y = y')
x = x' + Sf.y';
y = y';
can we get the matrix ...
1 Sf
0 1
to calculate what angel this give we know at (xmax', ymax')
xmax' = xmax + Sf.ymax;
this form a right angle triangle with
if we measure from the Y axis anti cloclwise (rememeber the Y axis is inverted)
the opposite site is ymax'
and the adjacent is Sf.ymax'
so Tan(angle) = (ymax'/Sf.ymax')
or angle = arctan( 1/ Sf );
so: at 0 the angle is 90Deg
as you increase Sf the angle decreases until at 1 it is 45 deg
at 10 it is about 5deg
if you st Sf to a -ve number then the angle goes above 90deg
i.e. -ve shears left, +ve shears right
for Y:
can we get the matrix ...
1 0
Sf 1
-ve shears down, +ve shears up
Demo Code
in the demo code I have two shears
beause the order matters ...
1 Sfx 1 0 (Sfx*Sfy+1) (Sfx)
0 1 * Sfy 1 = (Sfy) 1
AND
1 0 1 Sfx 1 (Sfx)
Sfy 1 * 0 1 = (Sfy) (Sfx*Sfy+1)
you can see that both have determinants of one
Determinant of
(Sfx*Sfy+1) (Sfx)
(Sfy) 1 is (Sfx*Sfy+1) - (Sfx*Sfy);
which is ONE!
this means that shearing does not alter the area of the shape
GBA doing it the nice way ...
Luck would have it but the Params in the rotate structure are
numbered exactly as you would expect.
the a,b,c,d that I have been using in the matricies like
are the same as the paramA, paramB, paramC, paramD in the most docs on rotate/scale/shearing.
they are two's complement fixed point numbers. (basically to convert to an integer divdide by 256)
this means (for those who have not read the Pern Tutorial) that the following works
to delare
signed short sum; //!
to assign from a integer value
result = (b*256);
to assign from a float value
result = (signed short)(b*256);
to add
result = a + b;
to subract
result = a - b;
to divide
result = a / b;
to multiply
result = (a * b)>>8;
this is the only time you have to worry about the fact they are not signed integers
and if you remember long multiplication
10's 1's
a b
* c d
---------------------
a*d d*b
c*a a*d 0
---------------------
r1000 r100 r10 r1
---------------------
but if the numbers both had one decimal place
1's .1's
a b
* c d
---------------------
a*d d*b
c*a a*d 0
---------------------
r10 r1 . r.1 r.01
---------------------
the result has 2
the same is true with binary; we have 8 binary bits after the point in both numbers before the multiply
so we have 16 after, but we only want 8, so whe shift right by 8 bits to get back to only 8.
Matrix Maths
Determinants
for a matix
a b
c d
the determinant or modulus is
(a*d) - (b*c)
and will give you the change in area
Matrix Maths
to multiply two, two by two square matricies
a b w x
c d by y z
you will get one, two by two square matrix
(a.w + b.y) (a.x + b.z)
(c.w + d.y) (c.x + d.z)
to multiply a two by two square matricies with a one by two matix
a b x
c d by y
you will get one, one by two square matrix
(a.x + b.y)
(c.x + d.y)