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 > Sprite rotation

#152391 - Cave Johnson - Fri Mar 14, 2008 9:29 pm

Ive been working on a code for my simple geometry wars clone to rotate a sprite so it is oriented towards the point on the touch screen where is touched. I have very little clue on how to do this, so any help would be great. My code for the touch position is touch->py and touch->px, and the sprites position is gunPos->y and gunPos->x. Thanks for any help.

#152402 - Liazon - Sat Mar 15, 2008 1:41 am

I thought there was hardware to handle rotations on the DS

#152405 - tepples - Sat Mar 15, 2008 1:47 am

The function you're looking for is arctangent. See if Wikipedia's article about trig functions gives you any ideas.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#152417 - Cave Johnson - Sat Mar 15, 2008 4:17 pm

After reading that article, it seems like im going to need to use arctangeant. Could i use something like this to find the angle?

atan((gunPos->y - touch->py) / (gunPos->x - touch->px))

Thanks.

#152428 - Cydrak - Sat Mar 15, 2008 8:08 pm

Almost! But think about your signs. If you have +dy/+dx and -dy/-dx for example (same fraction, diff signs, diff angle), what happens then? And what if dx == 0?

Anyway, if you try it you'll quickly see the problem. It's not hard to code around--in fact that's already been done. Check out atan2(), which takes dy/dx as separate arguments.


Last edited by Cydrak on Sun Mar 16, 2008 6:45 am; edited 1 time in total

#152444 - Cave Johnson - Sun Mar 16, 2008 3:51 am

I see what your saying Cydrak, but that problem is way ahead of me. After playing around with the code for hours, i cant figure out how to make the angle that the formula is creating become the sprites angle. Regardless of whether or not the formula would even work, what would i do to relate the new angle to the sprites angle? Thanks for all of your help.

#152453 - Cydrak - Sun Mar 16, 2008 6:39 am

Okay, what have you got so far? Can you rotate sprites at all, and does the matrix stuff make any sense? There's a few different steps involved, so it would help to know where to start.

Cave Johnson wrote:
... how to make the angle that the formula is creating become the sprites angle ...

I'm not sure if you're asking about the math, or how to set up the sprite? The formula is relative to the coordinates used. If the X's and Y's are in screen coordinates, you can use the angle directly. If not, you'd have to convert first (like if you had the view scrolling and rotating, too).

On the hardware side, you need to set ATTR0_ROTSCALE, and then ATTR1_ROTDATA(n) to choose one of the 32 affine settings. Which then has to be filled with a 2x2 matrix.

TONC explains all this in quite some detail (although this is for tonclib on the GBA, not libnds, the sprite settings are the same). There's also a chapter on basic vector and matrix math, if you're looking for that.

#152467 - Cearn - Sun Mar 16, 2008 2:00 pm

atan2() from libm.a is a floating point routine, which doesn't work well for GBA/NDS purposes. It's result is also a floating point number between 0 and 2π, so you'd have to scale it before you can use it for (co)sine lookup.
The GBA BIOS has an integer-based ArcTan2 routine which could help out.

If you don't really need the angle itself, you can also simply normalize the distance vector. This will give you the cosine and sine that you need for the matrix directly.

The main code:
Code:

int x0, y0, x1, y1;      // Two coordinate pairs.

int dx= x1-x0, dy= y1-y0;         // Get distance
u32 norm= Sqrt(dx*dx + dy*dy);      // Calculate length
dx= Div(dx<<8, norm);               // cosine. <<8 for .8 fixed point.
dy= Div(dx<<8, norm);               // sine

matrix.pa=  dx;      matrix.pb=  dy;
matrix.pc= -dy;      matrix.pd=  dx;

Sqrt and Div are GBA BIOS functions. The DS doesn't have those, but it does have hardware sqrt and div that you can use. Just be careful that you use the correct number of fixed points in the numbers.

proof of concept

#152478 - Cave Johnson - Sun Mar 16, 2008 3:37 pm

After reading through your tutorial cearn, ive setup the affine transformation matrix, and the sprite is affine and i can rotate it with button presses. From here, what would be my next step to make the sprite rotate and orient itself towards touch position? Thanks.

#152479 - eKid - Sun Mar 16, 2008 4:06 pm

Quote:
Sqrt and Div are GBA BIOS functions. The DS doesn't have those, but it does have hardware sqrt and div that you can use. Just be careful that you use the correct number of fixed points in the numbers.

It still has Sqrt and Div SWIs, but no ArcTan functions. They're software calculated so they might be safer for when you want to avoid conflicts with the math hardware. (like in an interrupt)

#152490 - Cearn - Sun Mar 16, 2008 5:43 pm

Cave Johnson wrote:
After reading through your tutorial cearn, ive setup the affine transformation matrix, and the sprite is affine and i can rotate it with button presses. From here, what would be my next step to make the sprite rotate and orient itself towards touch position? Thanks.

That will depend on which method you want to use: calculate the angles with an arctan variant and look-up the sine/cosine values, or calculate the sine/cosines by normalizing the distance vector. Either way will work, but one may be more useful for your game than the other.

If you do need the angle, you need an arctangens. With the standard atan2, the following should work:
Code:

#include <math.h>  // for floating point math functions.

static const float  M_PI=3.14159265f;

//! Get integer angle ∈ [-256, +256]. Note: very slow.
int atan2i(int y, int x)
{
    float anglef= atan2(y, x);   // anglef in rad
    return (int)(anglef*256/M_PI);      // Type and range conversion.
}

Don't forget to link the math library (add -lm to LIBS in the makefile). It's probably better to get an pure integer version for atan2. This ancient thread (forum:3840) has a sweet little assembly version. To use it, remove the ".section .iwram " line (that's mainly for GBA) and copy the code into an .s file in the source directory. The makefile should pick it up and assemble it automatically. To call the function from C, add the appropriate declaration: int atan2asm(int y, int x).

The code I gave in the other reply already gives you the steps for the normalization method. It provides the correct sine and cosine values you need to use.

The exact form of the rotation matrix depends on the orientation of your sprites. The code I gave assumes the graphics point to the right. If this is different, the matrix will need to be changed a little.

#152499 - Cave Johnson - Sun Mar 16, 2008 7:45 pm

Cearn, ive been trying to use your second code, but im having several problems. I have no idea where to put the code in, I was thinking of in the void handleInput, but regardless, im getting "a function-definition is not allowed before '{' token" errors. Im also not sure how this code would work, what am i going to have to add or change to make it cause the sprite to point towards the touch position? Thanks.

#152501 - Cearn - Sun Mar 16, 2008 8:43 pm

Cave Johnson wrote:
Cearn, ive been trying to use your second code, but im having several problems. I have no idea where to put the code in, I was thinking of in the void handleInput, but regardless, im getting "a function-definition is not allowed before '{' token" errors.

This:
Code:
int atan2i(int y, int x)
{
    float anglef= atan2(y, x);
    return (int)(anglef*256/M_PI);
}

is the definition of a function called atan2i. You use it like you would any other function. You can't put the definition inside another function, because C doesn't allow that, so you place it outside them. Then, simply call the function somewhere:
Code:
void handleInput()
{
   // ... stuff ...
   
   // Get the angle.
   // NOTE: make sure the touch and gun positions use the same number
   // of fixed point bits.
   int angle= atan2i(touchY-gunY, touchX-gunX);

   // Correct for object orientation angle
   angle += ???
   
   // Look-up (co)sine and convert from .12 to .8 fixed point
   int cosa= COS[angle&511]>>4;
   int sina= SIN[angle&511]>>4;
   
   // Set matrix
   pa=  cosa;   pb= sina;
   pc= -sina;   pd= cosa;
}

Cave Johnson wrote:
Im also not sure how this code would work, what am i going to have to add or change to make it cause the sprite to point towards the touch position? Thanks.

This depends on the details of your situation. I can't tell you what you have to change because you haven't yet mentioned these details. In particular, we need to know the default orientation of your sprites: do they point to the right, up, to the left or down? This determines whether the angle should be offset by 0, 384, 256 or 128, respectively (or was it 0, 128, 256 or 384? I always get minus signs mixed up).

#152508 - tepples - Sun Mar 16, 2008 10:36 pm

Cearn wrote:
You can't put the definition inside another function, because C doesn't allow that

True, nested functions aren't in ISO C or C++, but GNU C allows nested functions as an extension.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#152647 - Cave Johnson - Tue Mar 18, 2008 9:47 pm

No dice. Ive finally got everything compiled, but the code is still not working, any ideas? Heres what I have so far:

if (keysDown() & KEY_TOUCH) {
int angle= atan2asm(touch->py-gunPos->y, touch->px-gunPos->x);

int cosa= COS[angle&511]>>4;
int sina= SIN[angle&511]>>4;

spriteRotation->hdx = cosa;
spriteRotation->hdy = sina;
spriteRotation->vdx = -sina;
spriteRotation->vdy = cosa;
}

hdx is the same as pa
hdy is the same as pb
vdx is the same as pc
vdy is the same as pd

If the code seems fine and you dont see any flagrant errors, I am slightly doughting that i coded the gunposition and touch position x and y's correctly, and am going to have to work on them a bit. Thanks for all of the help so far.

#152670 - SiW - Wed Mar 19, 2008 5:07 am

I have some poor code here plucked straight out of an old project, not exactly suited to the DS (because it uses floats for one thing.. IIRC the original usage wasn't even a real-time project) but I did a quick test and it let me rotate a sprite to point at something. Perhaps you can create an optimized version from it:

Code:

float AngleBetween( float x1, float y1, float x2, float y2 )
{
    float dx = x2-x1;
    float dy = y2-y1;
    float angle = 0.0f;

    // calculate angle
    if ( dx == 0.0f )
    {
        if ( dy == 0.0f )
            angle = 0.0f;
        else if ( dy > 0.0f )
            angle = PI / 2.0f;
        else
            angle = PI * 3.0f / 2.0f;
    }
    else if ( dy == 0.0f )
    {
        if  ( dx > 0.0f )
            angle = 0.0f;
        else
            angle = PI;
    }
    else
    {
        if  ( dx < 0.0f )
            angle = atan( dy / dx ) + PI;
        else if ( dy < 0.0 )
            angle = atan( dy / dx ) + ( 2*PI );
        else
            angle = atan( dy / dx );
    }

    // convert to degrees
    angle = angle * 180 / PI;

    return angle;
}


And the usage for setting sprite rotation:

Code:

spriteInfo->angle = (AngleBetween( x1, y1, x2, y2 ) / 360.0f) * 512 - 128;

#152691 - Cave Johnson - Wed Mar 19, 2008 7:47 pm

Thanks for the code SiW, im deffinently going to play around with it and see if i can make it work for me, but in the mean time, id appreciate any help with my code. Thanks.

#152698 - Cearn - Wed Mar 19, 2008 8:49 pm

SiW wrote:
I have some poor code here plucked straight out of an old project, not exactly suited to the DS (because it uses floats for one thing.. IIRC the original usage wasn't even a real-time project) but I did a quick test and it let me rotate a sprite to point at something. Perhaps you can create an optimized version from it:

Code:
float AngleBetween( float x1, float y1, float x2, float y2 )
{
    ... code ...

    // convert to degrees
    angle = angle * 180 / PI;

    return angle;
}


This is essentially what atan2 does; only the number of inputs and the range of the output is different. The routine by ecurtz I pointed to should be considerably faster.

Cave Johnson wrote:
No dice. Ive finally got everything compiled, but the code is still not working, any ideas?

It would help if you described what you're seeing and (for the third time) what the orientation of your gun-graphics is. Also print the angle (angle&511 to be precise) and make sure that you get the following numbers for the following locations:

Code:
  320    384   448

  256    gun     0 / 511

  192    128    64

If you don't, the angle calculation is incorrect. If you do, then you're matrix is off.

#152867 - Cave Johnson - Sat Mar 22, 2008 12:11 am

After printing the angle, no matter where i clicked, the read out was 0,0. I have no idea if this could be my problem, or that i may have just programmed the print angle wrong. To answer some of your questions, the sprite is at 0 degrees, facing right. When i touch anywhere on the screen, the sprite does absolutely nothing, so i think that rules out an incorrect angle calculation, and the sprite can be turned with butten presses, so i would think that the matrix is on. Could the problem be that im not handeling the touch screen correctly? Id post some of my code, but i have no idea where the problem area would be. Any ideas or help would be great. Thanks.

#152870 - silent_code - Sat Mar 22, 2008 1:18 am

i'm going to bed, but this came to my mind: print the sprites coordinates and the touch coordinates... then print any input to the angle calculation function.
well, that is just in case you didn't try already. ;^)

good luck and happy coding!

#152872 - Cearn - Sat Mar 22, 2008 1:42 am

Cave Johnson wrote:
After printing the angle, no matter where i clicked, the read out was 0,0. I have no idea if this could be my problem, or that i may have just programmed the print angle wrong.
That or, as you say later, there's something not right about how you're handling the positions. I checked the atan2asm routine and that seems to work fine, so the problem would have to be somewhere else.

Cave Johnson wrote:
To answer some of your questions, the sprite is at 0 degrees, facing right.
Thank you. Then the matrix you're using should work.

Cave Johnson wrote:
When i touch anywhere on the screen, the sprite does absolutely nothing, so i think that rules out an incorrect angle calculation, and the sprite can be turned with butten presses, so i would think that the matrix is on. Could the problem be that im not handeling the touch screen correctly? Id post some of my code, but i have no idea where the problem area would be. Any ideas or help would be great. Thanks.

The code you gave earlier is pretty much how it should be. What's missing from it are the types of the positions and how they're initialized. Are you sure you're reading the touch coordinates correctly?

ninjad:
And what silent_code said.

#152887 - Cave Johnson - Sat Mar 22, 2008 4:09 am

[quote=Cearn"]]The code you gave earlier is pretty much how it should be. What's missing from it are the types of the positions and how they're initialized. Are you sure you're reading the touch coordinates correctly?[/quote]
Im not really sure what you mean by this. I have no idea what the types of the positions and how to initialize them are, so i dont think i coded them in (unless somehow by accident). Could this be my problem? Also what do you mean by me not reading the touch coordinates correctly? Sorry for such a basic question, but being a beginner, i havent picked up all of the lingo yet. Thanks.

#152904 - Cearn - Sat Mar 22, 2008 12:48 pm

Cave Johnson wrote:
[quote=Cearn"]The code you gave earlier is pretty much how it should be. What's missing from it are the types of the positions and how they're initialized. Are you sure you're reading the touch coordinates correctly?

Im not really sure what you mean by this. I have no idea what the types of the positions and how to initialize them are, so i dont think i coded them in (unless somehow by accident). [/quote] In C, you always have to declare/define the variables that you're using. Things like
Code:
int x, y;
touchPosition touch;

are variable definitions. "int" and "touchPosition" are the types in this case. "touchPosition" is a struct defined in one of the libnds headers, and it's members will probably be integers.

The touch variable contains the touch coordinates, but only if you put them in there first! Simply creating a variable doesn't give them the proper values. In fact, they will just contain whatever was in that memory before. To fill the touch variable with the actual touchpad data, use
Code:
touch=touchReadXY();
My guess is that your code doesn't have this inside the main loop.

Cave Johnson wrote:
Could this be my problem?
Yes. Not knowing what your own code does or how the code libraries you're using should be used is always a problem. Such practices lead to cargo cult or voodoo programming, which are bad (mkay).

Cave Johnson wrote:
Also what do you mean by me not reading the touch coordinates correctly? Sorry for such a basic question, but being a beginner, i havent picked up all of the lingo yet. Thanks.

From what I can see, it seems you're not only new to NDS coding but programming in general and programming in C in particular. It would be best to let the NDS parts lie and focus on learning C first. By immediately diving into NDS programming, you're trying to run before you can walk. Learn to walk first.

If you have access to C(++) books, get them and read them in full. There are also tutorials on the web, but you have to be careful which ones you use: they may not always be of high quality. Many of the GBA (and perhaps DS too) tutorials are known for their bad coding style, for example. I hear this one is nice: http://www.cprogramming.com/tutorial/. Also read the FAQ and the MUL/B.

#152919 - silent_code - Sat Mar 22, 2008 5:12 pm

i didn't read everything, so this may be posted already by someone else.

remember, that you have to declare a commonly used variable (used in multiple source files [.c, .cpp]) in a header file (as extern, then define it in only ONE source file), that has to be included by all files, that need the variable (or what else is in the header file). if you just declare a variable in each file with the same name, things get messy, because you're effectively using different variables!

i don't know if that applies to you problem, though. depends on your c/c++ knowledge.

as always, happy coding! :^)

#152924 - Cave Johnson - Sat Mar 22, 2008 6:37 pm

After clearing that up a bit, i understand what you are saying, and i have (purposely :) filled in the touch variables using touch=touchReadXY();. This seems to have narrowed down my problem quite a bit, and i think it might be that the touch x and y values in:

int angle= atan2asm(touch->py-gunPos->y, touch->px-gunPos->x);

were not coded correctly. Im going to have to fiddle around with them a bit. As for that tutorial, http://www.cprogramming.com/tutorial/ is the tutorial that i have been coding alongside the entire time. Thanks.