gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Beginners > Affine rotation around a centrepoint

#76483 - Ultima2876 - Wed Mar 22, 2006 2:01 am

Code:
void affinec(int affine_no, int angle, int scalex, int scaley, s16 *x, s16 *y, s16 xcentre, s16 ycentre)
   {
      signed int tempx = 0;            //variable declerations
      signed int tempy = 0;
      signed int rotx = 0;
      signed int roty = 0;
      s16 pa = 0;
      s16 pb = 0;
      s16 pc = 0;
      s16 pd = 0;
      
      affine(affine_no, angle, scalex, scaley);         //this fills the affine matrix with correct values - it is pre-tested and fills them correctly
      
      pa = sprites[(affine_no << 2)].attribute3;         //set pa, pb, pc, pd (just to make the code below clearer)
      pb = sprites[(affine_no << 2) + 1].attribute3;
      pc = sprites[(affine_no << 2) + 2].attribute3;
      pd = sprites[(affine_no << 2) + 3].attribute3;
      
      relative_x = *x - xcentre;      //the main bit
      relative_y = *y - ycentre;
      
      rotx = ((pa * relative_x) >> 8) + ((pb * relative_y) >> 8);
      roty = ((pc * relative_x) >> 8) + ((pd * relative_y) >> 8);
      
      *x = rotx + xcentre;
      *y = roty + ycentre;
   }


That's the code I'm using for this. My aim to have a sprite rotating around a given centrepoint. I saw code very similar to mine in another topic, and with a very similar problem - however, I have combed through the words in that topic for hours now looking for something I've missed.

The problem is, the sprite simply flies around the screen, flashing everywhere. After hours and hours of fiddling, I've decided that I can't find my mistake, and hopefully someone here will be able to help..

The affine function fills the affine matrices, and I use it a lot. It works perfectly - so that's not the problem. I've also tested various things - it seems that if I take away the *x and *y from the setting of relative_x/y I can get it to rotate correctly around centrepoint 0, 0 (that is, screen co-ordinate) - and it treats the xcentre and ycentre as if they were he sprite's co-ordinates... but I want to be able to specify the centrepoint!

Thanks in advance =P

#76547 - Cearn - Wed Mar 22, 2006 5:19 pm

Ultima2876 wrote:
.. and it treats the xcentre and ycentre as if they were he sprite's co-ordinates. ...

That's because the way you're using it now, xcenter and ycentre are screen and sprite coordinates. Or something like that, depending on what you mean by sprite coordinates. :P

In any case, you're missing a number of important pieces of information in this function, and not properly defining the ones that you do have. Most importantly, you have two spaces, screen and 'texture' (VRAM) space. In both of these, you have the origins for the actual affine transformation, and the desired affine transformation origins. That's four points, and you have two.
Define:
P : the affine matrix
cp=(cpx,cpy) : actual affine origin in texture space (sprite center)
cq=(cqx,cqy) : actual affine origin in screen space (sprite center on screen)
p0=(px0,py0) : desired affine origin in texture space
q0=(qx0,qy0) : desired affine origin in screen space
s=(w,h) : sprite size
m : scale parameter (= isDoubleSize ? 1 : ?)
x=(x,y) : correct sprite coordinates in screen space.

By definition,
cq = x + m*s
cp = s/2

Using these definitions, it is possible to derive that
x = (q0 - m*s) + P^-1 * (s/2-p0)
What you are currently using is
x = xcentre + P*(x - xcentre)
That is, no accounting for the actual affine origins, and using P instead of its inverse. And you have to use its inverse because you're correcting for transformation in screen-space, not texture space like you would do for affine backgrounds.

At this point it's merely a matter of writing it down in code. But because of the matrix inversion, sprite size and double-size flag status involved, I'm leaving this up to you :P

Oh, and be careful where you leave your fixed point!

ETA: fixed the size correction in texture space.


Last edited by Cearn on Wed Mar 22, 2006 11:17 pm; edited 1 time in total

#76573 - Ultima2876 - Wed Mar 22, 2006 9:45 pm

Would this be something like:

Code:
      *x = (((*x + xcentre) - ((doublesize * width) >> 8)) + (((pa * (width - xcentre)) + (pb * (height - ycentre)))) >> 8);
      *y = (((*y + ycentre) - ((doublesize * height) >> 8)) + (((pc * (width - xcentre)) + (pd * (height - ycentre)))) >> 8);


After obviously doing some 1/pa 1/pb etc...

Or am I still not getting it? :P

EDIT: almost forgot - doublesize is either 256 (1) or 128 (1/2) -- that's what the >> 8 after the first part is about ( (doublesize * width) >> 8 ) =P

#76579 - Cearn - Wed Mar 22, 2006 11:14 pm

Ultima2876 wrote:
Would this be something like:

Code:
      *x = (((*x + xcentre) - ((doublesize * width) >> 8)) + (((pa * (width - xcentre)) + (pb * (height - ycentre)))) >> 8);
      *y = (((*y + ycentre) - ((doublesize * height) >> 8)) + (((pc * (width - xcentre)) + (pd * (height - ycentre)))) >> 8);


After obviously doing some 1/pa 1/pb etc...

Or am I still not getting it? :P

EDIT: almost forgot - doublesize is either 256 (1) or 128 (1/2) -- that's what the >> 8 after the first part is about ( (doublesize * width) >> 8 ) =P

Uhmmmmm ...
*squints at the forest of parentheses*
Well, I think you're getting there, but I don't think it's quite right yet.

I take it that
- pa, pb, pc, pd here form the proper inverse of P? This is more than just doing pa=1/pa; you need to do the full rotation-scale inversion if you are planning on using both.
- every variable here is .8 fixed points. including width, height, x, y, etc. I'm asking because this is so easy to miss.
- width and height are the .8 fixed point version of the OAM-given sizes, not whatever you're using for the bounding box, or whatever.

I'm still curious about what the input (x,y) and (xcentre, ycentre) are. As far as I can see, (xcenter, ycentre) are the p0 in my equation. That is, the rotation point in texture space. But there is no direct equivalent of q0 yet. I think that's what the input (x, y) are supposed to be, but you're still adding (xcentre, ycentre) to those, which you probably shouldn't. Other than that, it just might work.

Oh, and remember you don't have to do everything in fixed point. The BIOS call BgAffineSet, for example, uses a .8 fixed point texture/(map) origin, but standard integers for the screen point. You could use normal integers for (x,y) and (w,h). The double-size correction can be done via a simple rightshift as well, rather than a multiplication + rightshift. The compiler will do this for you anyway, but doing it yourself cleans up the code a bit.

Oh and, uhm, I think I've noticed a little mistake in my formula. It should be P^-1 *(s/2-p0). I forgot the half. Sorry about that, I'll fix it as soon as I'm done with this post.

Personally, I was thinking of something like this, but you'll have to see how well it works for you.
Code:

// pseudo code
// oe : OAM_ENTRY to work on
// px0, py0 : desired texture org; input; .8 fixed
// qx0, qy0 : desired screen org;  input; .0 fixed

int w, h;      // width, height of OAMoe; .0 fixed
int dx, dy;      // screen offset q0 - ms ; .0 fixed
int ax, ay;      // 'anchor' a = p0-s/2
int aa, ab, ac, ad; // A = P^-1 ; .8 fixed

// Get w, h and m from OAM_ENTRY (example)
w= oam_size_lut[oe->attr0>>ATTR0_SHAPE_SHIFT][oe->attr1>>ATTR1_SIZE_SHIFT][0];
h= oam_size_lut[oe->attr0>>ATTR0_SHAPE_SHIFT][oe->attr1>>ATTR1_SIZE_SHIFT][1];
int m= (oe->attr0 & ATTR0_DOUBLE_SIZE) ? 0 : 1)

// calculate base offsets
dx= qx0 - (w>>m);   dy= qy0 - (h>>m);   // .0
ax= px0 - (w<<7);   ay= py0 - (h<<7);   // .8

{ Get A somehow }

// correct for transformed anchor offset (.8*.8 -> .0)
dx -= (aa*ax + ab*ay)>>16;
dy -= (ac*ax + ad*ay)>>16;

oam_set_pos(oe, dx, dy);

#76589 - tepples - Thu Mar 23, 2006 12:18 am

Cearn wrote:
- pa, pb, pc, pd here form the proper inverse of P? This is more than just doing pa=1/pa; you need to do the full rotation-scale inversion if you are planning on using both.

Which isn't hard. You compute one matrix with theta and scale; you compute the other with 65536/scale and -theta.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#76669 - Cearn - Thu Mar 23, 2006 9:55 am

tepples wrote:
Cearn wrote:
- pa, pb, pc, pd here form the proper inverse of P? This is more than just doing pa=1/pa; you need to do the full rotation-scale inversion if you are planning on using both.

Which isn't hard. You compute one matrix with theta and scale; you compute the other with 65536/scale and -theta.

True.
Unless scale_x and scale_y can be different. If they're not the same, you have a structurally different matrix. Compare this to this. It's more than just reversing the angle and using the reciprocal of the scales. Not very different, but enough so that you can't use the standard affine function to get there.

#76893 - Ultima2876 - Sat Mar 25, 2006 11:02 pm

Almost there!

You guys have been an enormous help... there's just one tiny, tiny problem.

If xcentre and ycentre are defined as p0, and are .8 fixed values equal to 0x0100, 0x0300, or 0x0500 (or any negative equivalent), the sprite disappears (I assume by being put offscreen) at certain angles (seems to be about 180-270 and 0-90 degrees, but not exactly those ranges). Any ideas what might cause this?

Of course, this won't be much of a problem, but it'd be nice to get it sorted.

Anyway, thanks so much, you've been awesome =P

#76898 - Cearn - Sat Mar 25, 2006 11:22 pm

My first guess would be a signed/unsigned short gone bad somewhere. Might be worth checking those again. Or overflow maybe, though the values in the whole thing should be relatively small. And it's only, odd pixels that go wrong? 0x0200, 0x0400, etc are alright?

Consider tracing the temporary values and the oam entries to see what's really going on. That should give a good clue. For more, we might have to see more code or the binary in question to play with.