3
votes

I have been working for too long on the business applications it seems... And graduated too long ago perhaps :) Recently i have been tasked writing a small simulation of robotics using C (although this question is more math/algorithm than C) where i have two units (tank-bots) starting at an X and Y coordinate on a playfield.

Now there are keys on a panel to rotate them, and a key to move them forwards. I am now facing a minor brain meltdown on the translation from degrees of rotation to the next X,Y coord to move to in the cartesian playfield.

Due to limitations in HW only fixed point is available for the actual movement, but calculations can be done by float values.

I wrote the following code just from memory just now:

/* Recalculate to radians */

int radians;



/* Use sin and cos to get a vector (new x and y coords). Translate from polar to   
   cartesian coordinates */
radians = (int) _tanks[0].rotationAngle * (M_PI / 180);
_tanks[0].x += _tanks[0].speed * cos(radians);
_tanks[0].y += _tanks[0].speed * sin(radians);

radians = (int) _tanks[1].rotationAngle * (M_PI / 180);
_tanks[1].x += _tanks[1].speed * cos(radians);
_tanks[1].y += _tanks[1].speed * sin(radians);

Unfortunately it seems my brain is not really refreshed on polar coordinate math and geometry after all these years on writing pure biz software, so it seems to not work as intended.

For instance if rotationAngle is 180, instead the next x/y is to the left, causing the bot to topple over :)

What i want is a movement scheme similiar to the old Micro Machines games if you remember where the next point would be in front of where the object is facing, so it moves (speed) number of steps there.

Can someone suggest where i'm going wrong here...

Also, if there is a smoother way of doing this in C than the pure mathematical attempt that i just wrote (badly at that), give me a hint.

EDIT:

Tried to add :

 float radians;


 radians = (45 - _tanks[0].rotationAngle) * (M_PI / 180);
 _tanks[0].x += (int) (_tanks[0].speed * cos(radians));
 _tanks[0].y += (int) (_tanks[0].speed * sin(radians));

according to answer below as 0 degrees is indeed the positive Y-axis. But this also gives incorrect results. Now movement for 180 degrees starting point is upwards to the left. At 180 degrees should be movement along the negative Y axis.

Some more code:

Init of _tank struct -

tanks[0].acc = 0;
tanks[0].dec = 0;
tanks[0].rotationAngle = 180;
tanks[0].speed = 0;
tanks[0].x = 400;
tanks[0].y = 150;
tanks[0].turretRotationAngle = 180;

The rotation degree is just a number (fixed integer), and i wrap it around as according to the circle @ 360 degrees, like so -

switch(direction) {
case 0:
    tank->rotationAngle -= degrees;
    if(tank->rotationAngle < 1) {
        tank->rotationAngle = 360;
    }
break;
case 1:
    tank->rotationAngle += degrees;
        if(tank->rotationAngle > 360) {
        tank->rotationAngle = 0;
    }
break;

}

One key for rotating clockwise, one for counter clockwise.

The rotation works, but the movement does not, as described...

Results of debug run:

Initial state (no movement due to 0 speed) -

radians = -2.3561945
x = 400
y = 150 
speed = 0

After movement (speed > 0) -

radians = -2.3561945 (the same since i only press the move button)
x = 399
y = 149 
speed = 2

This seems odd. The X coord should not change at all if the rotation is 180 degrees from the initial origin right? Only the Y should change, and in the opposite direction. I would translate the change instead to if speed is 2, vector length should be 2 so change would be 2 steps in the direction the object is facing, so y = y + 2 and x = x + 0 for 180 degree rotation on the object?

I feel like i'm getting there :)

Further EDIT:

Seems to be ALMOST correct along the lines of what i need for the playfield if i do this:

 radians = (_tanks[0].rotationAngle - 90) * (M_PI / 180);

Note -90...

Still when speed is lowered it seems to glitch but at least it moves in the right direction.

2
radians is going to be a number between 0 and 2PI, i.e. 0 to 6.28. Therefore radians must be a float.user3386109
With a rotation angle of 180 the next x/y should be to the left. What did you expect? cos(180 deg)= -1, sin(180 deg)=0.Henrik Carlqvist
... and that seems to be the only functional problem with this code. You might also consider a double since M_PI is one.Tommy
Single-step the code and look at the value of radians. An int value isn't going to work very well, as @user3386109 already noted. If you can't single-step, add printf() statements to see what the values are during the computation. When you know what the values are it will help you figure out why the code isn't doing what you expect.steveha

2 Answers

4
votes

For instance if rotationAngle is 180, instead the next x/y is to the left, causing the bot to topple over :)

Yes, that is what your code does: your code is correct, aside from the int radians issue that user3386109 mentions above, provided that 0° is the positive x-axis, 90° is the positive y-axis, 180° is the negative x-axis, 270° (or -90°) is the negative y-axis.

I'm guessing that instead, you want 0° to be the positive y-axis? And — do you want 90° to be the positive x-axis (so your angles proceed clockwise around the circle), or the negative x-axis (so they proceed counterclockwise)? For the former (clockwise) case, just change _tanks[...].rotationAngle to (90 - _tanks[...].rotationAngle) (to "flip" around the 45° line); for the latter (counterclockwise) case, just change it to (_tanks[...].rotationAngle + 90) (to "rotate" it 90° about the origin).

1
votes

@ruakh and @user3386109 well discuss the issues about angle units and phase.

In addtion, for a "smoother way of doing this in C" also consider:

  1. Use round(), else code will introduce a bias. (Assuming _tanks[1].x is some integer)

    double radians = _tanks[0].rotationAngle * (M_PI / 180);
    _tanks[0].x += (int) round(_tanks[0].speed * cos(radians));
    
  2. Use float rather than double as the extra precision with its longer calculation time are not needed.

    float radians = _tanks[0].rotationAngle * (float)((M_PI / 180));
    _tanks[0].x += (int) roundf(_tanks[0].speed * cosf(radians));  // note function names
    
  3. If processing time is limited, an integer look-up-table could be used with 360 int scaled sine and cosine values rather than all the floating point math.

    _tanks[0].x += (_tanks[0].speed * LUT_cos[_tanks[0].rotationAngle])/scale;