• Advertisement
Sign in to follow this  

How do I calculate if I should be adding or subtracting rotation?

This topic is 2104 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

(I'm an XNA/C# Newbie).

I currently have an Asteroids-style character that is controlled by the mouse.

However, I'm having an issue when my ship's movement would have it turn across the "west" line.

[img]http://oi43.tinypic.com/mvlufm.jpg[/img]

This is my current code for rotating:

[CODE]
if (destAngle - rotation <= 0)
{
rotation -= movingRotationSpeed;
}
else
{
rotation += movingRotationSpeed;
}
[/CODE]

However, an issue arises when the mouse crosses over the "west" line (with respect to the sprite). The destAngle changes from -3.140 to +3.140 (radians/PI). If I'm going clockwise at the time, the ship will then do a massive anti-clockwise movement (and vice verse).

What computation should I be doing to determine if the ship should move clockwise or anti-clockwise given I have its current angle and the destination angle?

Here is my entire code segment for that method:

[CODE]
MouseState mouseState = Mouse.GetState();
Vector2 mouseLoc = new Vector2(mouseState.X, mouseState.Y);

Vector2 direction = position - mouseLoc;
destAngle = (float)(Math.Atan2(-direction.Y, -direction.X));

if (mouseState.LeftButton == ButtonState.Pressed)
{
Console.WriteLine(rotation + " + " + destAngle);

if (destAngle - rotation <= 0)
{
rotation -= movingRotationSpeed;
}
else
{
rotation += movingRotationSpeed;
}

if (position.X == mouseState.X && position.Y == mouseState.Y)
{ }
else
{
//The ship always moves "forward".
position.X += speed * (float)(Math.Cos(rotation));
position.Y += speed * (float)(Math.Sin(rotation));
}
}
[/CODE]

Share this post


Link to post
Share on other sites
Advertisement
If what I think you mean is correct, you cannot have a naturally smooth rotation transition if you only use velocity (= rotation). You need to implement acceleration in order to obtain smooth transitions. Basically instead of deriving your velocity from the mouse position, you derive your acceleration from the mouse position, add the acceleration to the velocity, and finally add the velocity to the resulting position. This will give you more or less smooth changes of direction depending on how strong you make the acceleration.

Also you do not need angles in your code here. If you look closely you are doing an inverse tangent of your mouse coordinates to obtain the rotation angle, and then proceed to undo that operation by reconstructing the rotation vector with sin and cos. You could simply normalize the rotation vector and use that directly. You will need to do that to implement acceleration as it is much nicer with vectors than with angles.

Share this post


Link to post
Share on other sites
Not what I mean at all. I'm asking for what calculation I need to compare the current angle and the destination angle in order to determine whether to rotate the ship clockwise or counterclockwise.

Share this post


Link to post
Share on other sites
What I would do is something along the lines of the following:
[CODE]
targetRotation;
otherTargetRotation = (targetRotation < 0) ? targetRotation + 2*PI : targetRotation - 2*PI;
if(abs(targetRotation - rotation) > abs(otherTargetRotation - rotation)
{
//go one way
} else
{
//go the other
}
[/CODE]

Note: This is totally untested, but sounds good in my head.

Share this post


Link to post
Share on other sites
[quote name='Zael' timestamp='1334595812' post='4931780'][CODE]otherTargetRotation = (targetRotation < 0) ? targetRotation + 2*PI : targetRotation - 2*PI;[/CODE][/quote]

I'm not sure I understand this syntax? But that aside.

I've disabled forward motion for the purposes of testing without it going off the screen.

Your solution has seemingly worsened the problem. Whenever the destination is above the X-axis, it turns to face left and then stops rotating. Now given that it is impossible for it to not rotate (there's no if condition where it doesn't rotate): that means that it reaches an argument where both statements are true, and the ship is turning up then down again instantly.

Share this post


Link to post
Share on other sites
The question mark colon is a ternary operator for simplifying an if else block used for assignment.

http://en.wikipedia.org/wiki/Ternary_operation

Share this post


Link to post
Share on other sites
What that line says is if the targetRotation is less than 0 set the otherTargetRotation to targetRotation + 2PI; otherwise, set it to targetRotation-2PI.

Here is complete working code with the missing bits filled in. This has been tested and works.

[CODE]
#include <iostream>
#include <cmath>
using namespace std;
#define PI 3.14159265
int main()
{
cout << "Current Rotation?\n";
float currentRotation;
cin >> currentRotation;
cout << "Target Rotation?\n";
float targetRotation;
cin >> targetRotation;
while(currentRotation != targetRotation) //rotate
{
int direction = targetRotation - currentRotation > 0 ? 1 : -1;
float otherTargetRotation = targetRotation < 0 ? targetRotation + 2*PI : targetRotation - 2*PI;
cout << "Target: " << targetRotation << endl;
cout << "Other: " << otherTargetRotation << endl;
char c;
cin >> c;
if(abs(targetRotation - currentRotation) > abs(otherTargetRotation - currentRotation))
{
direction *= -1;
}
currentRotation+=.1*(float)direction;
if(currentRotation > PI) currentRotation-= 2*PI;
if(currentRotation < PI*-1.0f) currentRotation+=2*PI;
if(abs(targetRotation-currentRotation) < 0.1) currentRotation = targetRotation;
cout << "Current Rotation: " << currentRotation << endl;
}
}
[/CODE]

You will obviously want to set your own thresh hold and velocity, and remove the input/output statements.

EDIT: Some of my code was cut-off in my copy-paste. It should now be fixed.

Share this post


Link to post
Share on other sites
Zael's method is written in C++ rather than C#. He uses cin and cout to set the variables for the rotation.



[quote name='Zael' timestamp='1334601871' post='4931810'][code]
cout << "Current Rotation?\n";
float currentRotation;
cin >> currentRotation;
cout << "Target Rotation?\n";
float targetRotation;
cin >> targetRotation;
[/code][/quote]
>> sets the value entered on the command line into the variable that follows the >>. Similarly bout << prints whatever follows into a command prompt.

You will want to remove all the cin and cout anyways and set the variables according to your game. The important part to understand is


[quote name='Zael' timestamp='1334601871' post='4931810'][code]
while(currentRotation != targetRotation) //rotate
{
int direction = targetRotation - currentRotation > 0 ? 1 : -1;
float otherTargetRotation = targetRotation < 0 ? targetRotation + 2*PI : targetRotation - 2*PI;
if(abs(targetRotation - currentRotation) > abs(otherTargetRotation - currentRotation))
{
direction *= -1;
}
currentRotation+=.1*(float)direction;
if(currentRotation > PI) currentRotation-= 2*PI;
if(currentRotation < PI*-1.0f) currentRotation+=2*PI;
if(abs(targetRotation-currentRotation) < 0.1) currentRotation = targetRotation;
}
[/code][/quote]
as that is the part that does the actual rotation. What do you not understand in this part?

Share this post


Link to post
Share on other sites
[left][background=rgb(250, 251, 252)]Sorry, I was editing that post as you wrote. I managed to successfully translate the code, after Googling all the syntax I didn't understand. But thank you for also clarifying.[/background][/left]
___

If I use the while loop as provided in the code, the ship will instantly turn to face the cursor with no "turning" actually present. It just always looks at the cursor. However, while using the while-loop, the rotation itself is perfect.

However, changing the while to an if-loop gives me the "turning" I'm looking for. However, the issue I had with my own code is exactly the same in this code. Upon attempting to cross the west-axis, the ship attempts to do a 359-degree spin in the opposite direction. With forward motion enabled (rather than a stationary sprite), the ship just ~~~~~~~~ in a straight line.

Share this post


Link to post
Share on other sites
[quote name='3mptylord' timestamp='1334588737' post='4931753']
This is my current code for rotating:

[CODE]
if (destAngle - rotation <= 0)
{
rotation -= movingRotationSpeed;
}
else
{
rotation += movingRotationSpeed;
}
[/CODE]
[/quote]

Try this:
[CODE]
float diff = destAngle - rotation;
if (diff > Mathf.PI) {
destAngle-=Mathf.PI*2;
} else if (diff<-Mathf.PI) {
destAngle+=Mathf.PI*2;
}
if (Mathf.Abs(destAngle-rotation)<=movingRotationSpeed) {
rotation=destAngle
} else {
if (destAngle - rotation <= 0)
{
rotation -= movingRotationSpeed;
}
else
{
rotation += movingRotationSpeed;
}
}
[/CODE]

Share this post


Link to post
Share on other sites
It should work for crossing the Y-axis just as well. I will attempt an English translation of the algorithm.

First, get the direction of rotation to go to the current target. Then, create a second target. This second target is the same as the actual target but offset by 2 PI. If the target is less than 0 then add 2 PI to it to create the second target; otherwise, subtract 2 PI.

If for example we have a target of 1 radians, we now have targets of 1 and -5.28319 radians. Next, we identify which one is closer to the currentRotation. If it is our second target (-5.28319 radians in the example) then we change the direction. Let's assume the currentRadians are -3.1. The second target is closer so our direction should now be -1.

Now we take the currentRotation and add the movingRotationSpeed times the direction. This will get things rotating in the correct direction. The next bit is bookkeeping. If the currentRadians are greater than PI we want to reduce it by 2 PI. This keeps it in the same position but keeps the value between -PI and PI. We do the same test to check if currentRadians are less than -PI and if so, increase the value by 2 PI.

The final bit is because we are dealing with floats. When dealing with floats it is extremely unlikely that we will get an exact match. So once our currentRotation is within the movingRotationSpeed of the target we simply set it equal to the target.

Example:
currentRotation = -2.9
targetRotation = 2.5
movingRotationSpeed = .1

First we check the direction. Since targetRotation minus currentRotation is greater than zero we set the direction to 1.
direction = 1

Now we create the secondTarget. Because the targetRotation is greater than 0, our secondTarget will be targetRotation minus 2 PI.

secondTarget = -3.78318531

We can see that our currentRotation is closer to our secondTarget, so we change direction.

direction = -1

Now we move my movementDirection*direction.

currentRotation = -3.0 (iteration 1)
currentRotation = -3.1 (iteration 2)
currentRotation = -3.2 (iteration 3)

On iteration 3 the currentRotation becomes less than negative PI, so we add 2 PI to the result.

currentRotation = 3.08318531

On iteration 4 we find that the currentRotation is greater than targetRotation, so our initial direction is -1. We also find that targetRotation is now closer than our secondTarget, so our direction stays at -1 from here on out.

direction = -1

We continue iterating.

currentRotation = 2.98318531 (iteration 4)
currentRotation = 2.88318531 (iteration 5)
currentRotation = 2.78318531 (iteration 6)

currentRotation = 2.68318531 (iteration 7)
currentRotation = 2.58318531 (iteration 8)

On iteration 8 we find that we are within the movingRotationSpeed of our target, so we simply set the currentRotation equal to the targetRotation.

currentRotation = 2.5


Does that help/make sense?

P.S. Looks like SimonForsman's code does essentially the same thing in a little bit different order.

P.S.S. An if statement is actually more correct because the while loop should essentially be replaced with the game loop. Your condition should be if the angles are not equal do the code that I have in the loop.

Share this post


Link to post
Share on other sites
As some people here probably already know, I've been on a campaign to promote using unit-length complex numbers to represent 2D rotations. If you have an angle alpha, the corresponding complex number is (cos(alpha) + i*sin(alpha)).

Now, if I want to know whether I need to rotate clockwise or counterclockwise, I can do this:
[code] rotation_to_go_from_current_to_target = target * conj(current); // alternatively target / current, but multiplying by the conjugate is faster than dividing
if (rotation_to_go_from_current_to_target.imag() > 0) {
// Counterclockwise
}
else {
// Clockwise
}
[/code]

Share this post


Link to post
Share on other sites
[quote name='SimonForsman' timestamp='1334606861' post='4931845']
[quote name='3mptylord' timestamp='1334588737' post='4931753']
This is my current code for rotating:

[CODE]
if (destAngle - rotation <= 0)
{
rotation -= movingRotationSpeed;
}
else
{
rotation += movingRotationSpeed;
}
[/CODE]
[/quote]

Try this:
[CODE]
float diff = destAngle - rotation;
if (diff > Mathf.PI) {
destAngle-=Mathf.PI*2;
} else if (diff<-Mathf.PI) {
destAngle+=Mathf.PI*2;
}
if (Mathf.Abs(destAngle-rotation)<=movingRotationSpeed) {
rotation=destAngle
} else {
if (destAngle - rotation <= 0)
{
rotation -= movingRotationSpeed;
}
else
{
rotation += movingRotationSpeed;
}
}
[/CODE]
[/quote]

That code almost worked perfectly. But I can't explain how it's not working... as it seems to change. It seems that I can only draw two circles in any direction, and then it'll randomly draw the circle in the opposite direction. For example, clockwise, clockwise, attempt to go clockwise again but it turns counter-clockwise... but I can draw figures of 8 indefinitely.

I'm guessing I need to "wrap" the angle?

[CODE]
private float WrapAngle(float theta)
{
if (theta < -Math.PI)
{
theta += (float)(Math.PI * 2);
}
if (theta > Math.PI)
{
theta -= (float)(Math.PI * 2);
}
return theta;
}
[/CODE]

EDIT: The issue arises when the angle reaches ~8 (positive or negative). The rotation than starts dropping, rather than wrapping or just getting higher.

Share this post


Link to post
Share on other sites
[quote name='alvaro' timestamp='1334607881' post='4931851']
As some people here probably already know, I've been on a campaign to promote using unit-length complex numbers to represent 2D rotations. If you have an angle alpha, the corresponding complex number is (cos(alpha) + i*sin(alpha)).

Now, if I want to know whether I need to rotate clockwise or counterclockwise, I can do this:
[code] rotation_to_go_from_current_to_target = target * conj(current); // alternatively target / current, but multiplying by the conjugate is faster than dividing
if (rotation_to_go_from_current_to_target.imag() > 0) {
// Counterclockwise
}
else {
// Clockwise
}
[/code]
[/quote]

I have noticed this lately! But really take Alvaro's suggestion it will work better. I was hesitant at first when he suggested it for me a few weeks ago but I'm glad I did because it just made things much easier in the end and there were no special cases or anything to worry about.

Share this post


Link to post
Share on other sites
[quote name='Zael' timestamp='1334607118' post='4931846']P.S.S. An if statement is actually more correct because the while loop should essentially be replaced with the game loop. Your condition should be if the angles are not equal do the code that I have in the loop.[/quote]

Using an if-statement had the same issue as my original code. I couldn't draw a complete circle on the screen with the ship, as the moment the destination angle went from negative PI to positive PI it would attempt to turn in the opposite direction (the west-axis).

Share this post


Link to post
Share on other sites
Adding wrapping to Simon's code create the functionality I was looking for, but thank you everyone else (particularly you, Zael, as you've taught me some syntax techniques). :)

As you said Zael, Simon's solution was very similar to yours... I don't actually understand how his works and yours didn't. O_o

[CODE]public void mouse()
{

MouseState mouseState = Mouse.GetState();
Vector2 mouseLoc = new Vector2(mouseState.X, mouseState.Y);

if (mouseState.LeftButton == ButtonState.Pressed)
{

if (!position.Equals(mouseState))
{
Vector2 direction = position - mouseLoc;
float destAngle = (float)(Math.Atan2(-direction.Y, -direction.X));

position.X += speed * (float)(Math.Cos(rotation));
position.Y += speed * (float)(Math.Sin(rotation));

Console.WriteLine(rotation + " + " + destAngle);

float diff = destAngle - rotation;
if (diff > Math.PI)
{
destAngle -= (float)(Math.PI * 2);
}
else if (diff < -Math.PI)
{
destAngle += (float)(Math.PI * 2);
}
if (Math.Abs(destAngle - rotation) <= movingRotationSpeed)
{
rotation = destAngle;
}
else
{
if (destAngle - rotation <= 0)
{
rotation -= movingRotationSpeed;
}
else
{
rotation += movingRotationSpeed;
}
}

rotation = WrapAngle(rotation);
}

}
}

private float WrapAngle(float theta)
{
while (theta < -MathHelper.Pi)
{
theta += MathHelper.TwoPi;
}
while (theta > MathHelper.Pi)
{
theta -= MathHelper.TwoPi;
}
return theta;
}[/CODE]

Share this post


Link to post
Share on other sites
[quote name='3mptylord' timestamp='1334608018' post='4931853']
[quote name='SimonForsman' timestamp='1334606861' post='4931845']
[quote name='3mptylord' timestamp='1334588737' post='4931753']
This is my current code for rotating:

[CODE]
if (destAngle - rotation <= 0)
{
rotation -= movingRotationSpeed;
}
else
{
rotation += movingRotationSpeed;
}
[/CODE]
[/quote]

Try this:
[CODE]
float diff = destAngle - rotation;
if (diff > Mathf.PI) {
destAngle-=Mathf.PI*2;
} else if (diff<-Mathf.PI) {
destAngle+=Mathf.PI*2;
}
if (Mathf.Abs(destAngle-rotation)<=movingRotationSpeed) {
rotation=destAngle
} else {
if (destAngle - rotation <= 0)
{
rotation -= movingRotationSpeed;
}
else
{
rotation += movingRotationSpeed;
}
}
[/CODE]
[/quote]

That code almost worked perfectly. But I can't explain how it's not working... as it seems to change. It seems that I can only draw two circles in any direction, and then it'll randomly draw the circle in the opposite direction. For example, clockwise, clockwise, attempt to go clockwise again but it turns counter-clockwise... but I can draw figures of 8 indefinitely.

I'm guessing I need to "wrap" the angle?

[CODE]
private float WrapAngle(float theta)
{
if (theta < -Math.PI)
{
theta += (float)(Math.PI * 2);
}
if (theta > Math.PI)
{
theta -= (float)(Math.PI * 2);
}
return theta;
}
[/CODE]

EDIT: The issue arises when the angle reaches ~8 (positive or negative). The rotation than starts dropping, rather than wrapping or just getting higher.
[/quote]

you need to keep the current rotation between -2*PI and 2*PI for my solution to keep working. (so if it goes above 2*PI you subtract 2*PI , if it drops below -2*PI you add 2*PI). (you seem to be adding 2*PI when it drops below -PI which will change the angle, (a full rotation is 2*PI)

also, since you are using floats you can use Mathf instead of Math to avoid having to typecast all the time. (float (single precision) math may also be faster than double precision math on some platforms)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement