Rotation easing algorithm not working in XNA - c#

I am trying to ease (gradually move) the rotation of an object to an arbitrary position. The angle of rotation is determined by a Virtual Thumbstick class which returns X/Y coordinates between -1 and 1. If there is no movement on the thumbstick, I am rotating back to point to 0, except I am compensating for the angle of the sprite's image.
The problem I am having is that this code will only allow approximately 1.5 rotations (anywhere between -3*PI and 3*PI) instead of continuous rotation. Using Math.Atan2 with the X/Y coords of the thumbsticks, the returned angle is constrained between -PI and PI but allows continuous rotation. Also, if I rotate the object in one direction and release the thumbstick, it will rotate back to top from the direction it came. I want it to rotate back to the top on the shortest route.
if (VirtualThumbsticks.LeftThumbstick.Length() > .2f)
{
double rotateTo = Math.Atan2(VirtualThumbsticks.LeftThumbstick.Y, VirtualThumbsticks.LeftThumbstick.X);
if (rotateTo > Rotation + Math.PI) rotateTo -= (Math.PI * 2);
if (rotateTo < Rotation - Math.PI) rotateTo += (Math.PI * 2);
Rotation += (rotateTo - Rotation) * 0.2;
}
else
{
Rotation += (-1.57079 - Rotation) *0.2;
}
If there are any Flash/ActionScript game developers that know what I'm talking about, please chime in as I can apply that to C#.
Thanks in advance, everyone!
EDIT:
This chunk of code works flawlessly in AS3:
function enterFrameHandler(e:Event):void
{
var curMouseX = Math.round(-(arrow.x - stage.mouseX));//(stage.stageWidth/2)-(stage.mouseX/2);
var curMouseY = Math.round(-(arrow.y - stage.mouseY));//(stage.stageHeight/2)-(stage.mouseY/2);
var angleTo:Number = Math.atan2(curMouseX, -curMouseY) * TO_DEGREES;
if (angleTo > arrow.rotation+180) angleTo -= 360;
if (angleTo < arrow.rotation-180) angleTo += 360;
tf_angle.text = angleTo.toString();
tf_mouseX.text = curMouseX.toString();
tf_mouseY.text = curMouseY.toString();
arrow.rotation += (angleTo - arrow.rotation) * 0.2;
}
I'm beginning to wonder if there is an issue with my types or typecasting that is causing the problem. If anyone has any ideas, your input is greatly appreciated.

This is written in C# using XML, so the actionscript parallel may not apply, but i bet that it does. In Flash, rotations are adjusted at the end of every frame, because there is little benefit in storing a rotation transform of 123456789 degrees. so the transform resets to the modulo value of the rotation.
However, in calculating shortest rotation, you can run into problems if you are expecting some large number and comparing it to some other number instead of calculating their relative positions using their modulo values.
Maybe this is the source of the problem. its just a guess.
On the odd chance you are not familiar with it, the modulo operator - aka percent sign - "%" will give you the remainder of division. this is helpful for numbers that loop, like grid alignments, and rotations.
Example:
21 % 5 = 1;
720 % 360 = 0;

The issue has been solved. The problem was lying in the rotation value not being "normalized". Basically, if the rotation value exceeds Math.PI*2, it must return to 0... and if the rotation value falls below 0, it must return to Math.PI*2.
The last comment on this page has solved my problem and the rotation is normalized now.
http://forums.create.msdn.com/forums/t/27527.aspx
My resulting code:
if (VirtualThumbsticks.LeftThumbstick.Length() > .2f)
{
float angleTo = (float)Math.Atan2(VirtualThumbsticks.LeftThumbstick.Y, VirtualThumbsticks.LeftThumbstick.X);
if (angleTo > Rotation + Math.PI) angleTo -= (float)(Math.PI * 2);
if (angleTo < Rotation - Math.PI) angleTo += (float)(Math.PI * 2);
accelerationRotation += (angleTo - Rotation) * (float)0.25;
}
else
{
accelerationRotation += ((float)(Math.PI / 2) - Rotation) * (float)0.2;
}
Rotation = Wrap(accelerationRotation, 0, (float)Math.PI * 2);
The Wrap function is as follows:
public static float Wrap(float value, float lower, float upper)
{
unchecked
{
if (lower >= upper)
{
throw new ArithmeticException("rotary bounds out of negative or zero size");
}
float distance = upper - lower;
float times = (float)Math.Floor((value - lower) / distance);
return value - (times * distance);
}
}

Related

Rotating a square to point at a Vector2 gets completely messed up at 0 degrees

This is the code that rotates the boat to follow the line. A joint is just a Vector2 on the line. To the left is 0 degrees. And as you can see, when the boat is going from 0 to 360 or vice versa degrees, it glitches out.
float LookAt(Vector2 joint)
{
float deltaY = rect.y - joint.Y; // Calculate Delta y
float deltaX = joint.X - rect.x; // Calculate delta x
float angle = (float)(Math.Atan2(deltaY, deltaX) * 180.0 / Math.PI) + 90; // Find angle
float amountToRotate = angle - rotation;
amountToRotate *= 0.05f;
Console.WriteLine($"Rotation: {rotation} Angle: {angle} Amount: {amountToRotate}");
return rotation + amountToRotate;
}
I'm using an amountToRotate variable because I want the rotation to be a little smooth (doesn't show well on the GIF).
https://gyazo.com/cd907763665ac41a2c8f8e5d246ab292
Any help is much appreciated.
(I'm also doing this in Raylib if that makes any difference).
Because atan2() returns values between -PI and +PI or between -180 and +180 degrees.
So, if your boat is looking at something like 170 deg and the next joint is at 179 deg, then your amountToRotate is +9 deg, which is fine.
But, if your boat is looking at 180 degs and your the joint is at -180 deg, your amountToRotate is suddenly -360 deg (-180 - 180), which is facing right towards the positive x-axis. The you take 5% off of amountToRotate and add it to your current rotation (180 - 360*0.05 = 162) which means, that the boat is turning away from the node.
As a quick solution, you could convert the angle to full 360 degrees:
angle = (angle + 360) % 360;
But you will still get problems in the direction of the positive x-axis. The better solution would be to calculate the angle between two vectors and invert it:
angleTowardsV2FromV1 = -(Math.atan2(v1.y, v1.x) - Math.atan2(v2.y, v2.x))
in your case this would look something like:
angle = (-(Math.atan2(Math.sin(rotation*Math.PI/180), Math.cos(rotation*Math.PI/180)) - Math.atan2(deltaY, deltaX)))*180/Math.PI
And also, if you only take 5% of your angle, the rotation will never get there. I think, it would much wiser to clamp the angle to +-5deg:
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
angle = clamp(angle, -5, +5);
And the just return:
return rotation + angle;
I hope this helps.

Atan2 is giving the false angle when object's y or z change in Unity

I wrote a algorithm. Its normally working.
Just i can not get right angle of x when y or z changes
float x = Mathf.Atan2(transform.forward.y, transform.forward.z) * Mathf.Rad2Deg
This code is giving the right angle when my object angles are (x,0,0).
But when the y or z change(x,35,46), this code is giving false angle.
By the way i want to get 0-360 angle.
If i get this angle, code will work(i tested it).
So i am trying to get the rotation of x axis 0-360.
But the atan2 is not giving the right value.
Maybe i can use Vector3.Angle but it doesn't work that i want.
I don't ask too many questions in stackoverflow so if you didn't understand please tell me which part didn't you get it?
If I understand you correct you want the objects rotation around the X axis (global or local).
You could probably simply use Transform.eulerAngles something like
var x = transform.eulerAngles.x;
if(x < 0) angle += 360;
Or if you want the local rotation (relative to the parent) Transform.localEulerAngles
var x = transform.localEulerAngles.x;
if(x < 0) angle += 360;
No, I wouldn't figured out
int sign = (transform.forward.y<0) ? 1 : -1;
float x = (Vector3.Angle(transform.position, transform.forward) - 38) * sign * 180 / 100;
This code is just working on 0,90,0 angle
I still can not reach the right angle when the rotation change
I found some code with the combination of Cross,Dot,Angle:
float Angle360(Vector3 v1, Vector3 v2, Vector3 n)
{
float angle = Vector3.Angle(v1,v2);
float sign = Mathf.Sign(Vector3.Dot(n, Vector3.Cross(v1, v2)));
float signed_angle = angle * sign;
return (signed_angle + 180) % 360;
}
This code is not working too
It will be made probably with Vector3.Angle or Dot vs.
How can i find right angle with Vector3.Angle,
The value 38 is changing
Vector3.Angle(transform.position, transform.forward)
This code is showing the angle but when the rotation change it gives false value.
how can i get the angle of x when objects look change.
So this code is giving right when the value is x,0,0.
Mathf.Atan2(transform.forward.y, transform.forward.z) * Mathf.Rad2Deg
I think i am not using Vector3.Angle Correctly
I need to get the x value when the y and z values are different

How to calculate different speed averages based on an angle?

Here it goes, I am making a climbing system in a Unity project where depending on the direction your speed is changed. In my case it would be a speed of 1 for going up, 1.5 for sideways and 2 for going down.
This is how I calculate angles right now:
float angle = (Mathf.Atan2(this.characterController.GetAxisControlValue(CharacterAxisControl.Vertical), this.characterController.GetAxisControlValue(CharacterAxisControl.Horizontal)) * Mathf.Rad2Deg);
angle %= 360.0f;
if (angle < 0.0f)
{
angle += 360.0f;
}
GetAxisControl value returns a vale between -1 and 1. Now I need to find out how you would get the average speed between point like this: Example
I'm searching for a formula that can solve this problem.
Could anyone assist me please, pretty please.
If you want it to be proportional to the angle, then it's easy:
var speed = (angle % 180) / 180 + 1;
This will give you:
0 deg 1
90 deg 1.5
180 deg 2
270 deg 1.5
45 deg 1.25
150 deg 1.83 // this is your picture example
If you want any arbitrary speeds you could use linear interpolation. Let's say you want speed Vu for going up, Vs for going sideways and Vd down.
var t = (angle % 180) / 90; // we only care about vertical direction
var speed = t < 1
? Vu * (1 - t) + Vs * t // this is for picking the value in the range 0..90
: Vs * (2 - t) + Vd * (t - 1); // this is in range 90..180

Work out whether to turn clockwise or anticlockwise from two angles

Im making a game in XNA.
I have enemies and player.
The enemies should turn gradually towards the player. They should work out whether they need to turn clockwise or anticlockwise, whichever is shorter.
I got the angle the enemy is currently facing and the angle it should be facing (the angle of the line between the enemy and the player) as radians by using Atan2.
I get some weird behavior though. Lets say in the scenario below. the enemy might turn all the way around in the wrong direction.
My code (below) keeps getting longer and I'm still having issues. This code is part of the enemy classes Update method.
This must be a common problem to overcome in games. Is there some way of dealing with this?
//this bit is just in case enemy has rotated more than 360 degrees (gets set back to 0)
if (Math.Abs(_blocklist[0]._floor.Revolutions) >= 2)
{
_blocklist[0]._floor.Rotation = 0.0f;
}
//enemy rotation in radians
float blockroat = _blocklist[0]._floor.Rotation;
// vector to player - vector to enemy
_vectToPlayer = playerpos - _blocklist[0].Centre
angletoplayer = (float)(Math.Atan2(_vectToPlayer.Y, _vectToPlayer.X));
diff = blockroat - angletoplayer;
if (diff < -Math.PI)
{
diff += (float) Math.PI;
diff = -diff;
}
else if (diff > Math.PI)
{
diff -= (float)Math.PI;
diff = -diff;
}
// if enemy angle if off by a certain amount
if (Math.Abs(diff) >_maxturn)
{
if (diff < 0)
{
//turn clockwise
_blocklist[0]._floor.Rotation += _maxturn;
}
else
{
//turn anti clockwise
_blocklist[0]._floor.Rotation -= _maxturn;
}
}
UPDATE
I ended up using method 2 like this.. Works perfectly. Also it is a lot neater than my previous code
//enemy rotation in radians from farseer (red line)
float brot = _blocklist[0]._floor.Rotation + ((float)Math.PI/2);
//vector from enemy to player (blue line)
Vector2 _vectToPlayer = playerpos - _blocklist[0].Centre;
//cross product of 2d vectors
cross = (_vectToPlayer.X * (float)Math.Sin(brot)) - ((float)Math.Cos(brot) * _vectToPlayer.Y);
//tolerance for how closely enemy must point towards player
if (Math.Abs(cross) > 5)
{
if (cross > 0)
{
//turn anticlockwise
_blocklist[0]._floor.Rotation -= _npcstats.maxturnspeed;
}
else
{
//turn clockwise
_blocklist[0]._floor.Rotation += _npcstats.maxturnspeed;
}
}
I think that my previous code was more or less doing exactly the suggested method 1.
But I could not get it to work.. I put this down to the vagaries of farseers coordinate system + how it interacted with my own.
Technique #1:
You are using a convention that I'm not familiar with. In your convention, east is 0, north is -π/2, west is both π and -π, and south is π/2. All angles are between -π and π.
Normally the angle of a character facing east is zero, north is π/2, west is π, and due south is 3π/2. All angles are between 0 and 2π.
Let's assume the normal convention rather than your convention. Start by getting your red and blue vector angles correct in the normal convention; how you do that is up to you.
Subtract the angle of the red vector from both angles. Now we have the guy at the origin facing due east.
Now normalize the new blue angle; if it is smaller than 0, add 2π. If it is larger than 2π, subtract 2π. Do that until it is between 0 and 2π.
Now we have two angles; the angle of the new red vector is zero and the angle of the new blue vector is between 0 and 2π.
If the angle of the new blue vector is less than π then the character at the origin needs to turn towards its left. If it is greater than π then turn right.
Technique #2:
Take a non-zero point on your blue and red vectors, say (bx, by) and (rx, ry). Now compute bx * ry - by * rx. If it is positive, turn right, if it is negative, turn left. If it is zero then either they are facing directly towards or directly away; in that case you'll have to figure out which case you're in by some other means. (This is essentially Jacek's answer stated more directly.)
If you have both blue and red vectors as Vector3, you can do:
Vector3 crossProduct = Vector3.Cross(red, blue)
if (crossProduct.z > 0)
// Turn Right
else
// Turn Left
very easy. say you got 2 angles alpha and beta and you would like to tell shortest movement from alpha to beta is clockwise or anti clockwise. what you should do is this:
set alpha to be 0 and add the same offset you gave alpha to beta.
now if beta is above 180- movement is anticlockwise, else, movement is clockwise.
for example: alpha is 10 deg and beta is 350 deg. so -10 deg offset to both angles sets alpha to be 0 and beta to be 340 and movement is anticlockwise.
def direction_by_2_angles(alpha, beta)
# true clockwize, false not clockwize
delta = 360 - alpha
beta = beta + delta
beta = beta % 360
beta < 180 ? true : false
end
I searched to find if there is an super-short answer. Didn't found one. Seem like Technique 1 is popular. However i already implemented it so here you are:
//ccw = 1, cw = -1
//refineAngle = normallize angle to range [-PI, PI]
currentAngle = refineAngle(currentAngle);
targetAngle = refineAngle(targetAngle);
if(targetAngle < 0)
targetAngle += (PI *2);
if(currentAngle < 0)
currentAngle += (PI *2);
if(targetAngle < currentAngle)
targetAngle += (PI *2);
if(targetAngle - currentAngle <= PI)
return 1;
else
return -1;

Getting a proper rotation from a vector direction

I currently have this code in my game:
Vector2 pixelpos = new Vector2(x, y);
Vector2 center = new Vector2(t.Width / 2, t.Height / 2);
Vector2 pixelposWorld = (pixelpos - center);
float rotation = (float)Math.Atan2(pixelposWorld.Y, pixelposWorld.X);
float rotationPercent = (MathHelper.ToDegrees(rotation) / 360);
My goal is to end up with rotationPercent to be a value between 0.0 and 1.0, 0 degrees being 0.0, 180 being 0.5 and 360 being 1.0.
Currently, rotationPercent only comes out as 1.0.
What can I do to fix this?
First of all, in your case, there is no need to convert it to degrees, since Math.aTan2 returns the angle in radians, just divide your rotation variable by (2*Pi).
Secondly, check what you are doing at "t.Width / 2, t.Height / 2", as you haven't specified in your question what 't' is, make sure it's members are not integers.
Now as far as your problem itself, there is not enough information supplied. Where does this contain your rotation information? Is the 'pixelpos' vector your world space position, or do you also use that as rotation?
Brought back to a minimum, the following code works roughly like like you described?
Vector2 pixelpos = new Vector2(0, 1);
float rotation = (float)(Math.Atan2(pixelpos.Y, pixelpos.X) / (2 * Math.PI));
Which results 0.25, or 90 degrees.
The best way to calculate this would be through the use of dot products though:
Vector2 pixelposWorld = Vector2.Normalize(pixelpos - center);
float dot = Vector2.Dot(Vector2.UnitX, pixelposWorld);
float rotation = (pixelposWorld.Y >= 0)
? (1f - dot) / 4f
: (dot + 3f) / 4f;
Just saying, it is on average 120% faster (I ran the tests).
I think you forgot to use float in 360, it should be 360f.

Categories

Resources