I'm trying to use atan2 to turn a range of - 1 to 1 into radians and then from radians into degrees.
However atan2(0,1) is equal to 0 when it should be equal 90.0 what am I doing wrong here?
float radians = atan2(0, 1);
float degrees = (radians * 180 / PI);
if(radians < 0)
{
degrees += 360;
}
Edit: Okay so i've plugged in the values the right way round this time.
float xaxisval = controller->left_stick_x_axis();
float yaxisval = controller->left_stick_y_axis();
// plug values into atan2
float radians = atan2(yaxisval, xaxisval);
float degrees = (radians * 180 / PI);
if (radians < 0)
{
degrees += 360;
}
For context xaxisval and yaxisval are grabbing a value from an analog stick with a max value of 1 to the right and a minimum value of -1 to the left. So when i press the analog stick to the right the yaxisval is equal to 0 and the xaxisval is equal to 1.
This should return 90 degrees, as if you imagine the analog stick as a full 360 degree circle. Up direction is 360/0 right is 90 down is 180 left is 270 etc.
I stuck these values into the debugger and this is what it returned.
xaxisval: 1.00000
yaxisval: 0.00000
degrees: 0.00000
However I want this direction to come up as 90 degrees it has seemed to jumped up by 90 degrees and i tested the down position and it was equal to 90. Any suggestions?
Debugging Output:
Joystick Up position
xaxisval: 0.00000
yaxisval: -1.00000
degrees: 270.00000
Joystick Right position
xaxisval: 1.00000
yaxisval: 0.00000
degrees: 0.00000
Joystick Down position
xaxisval: 0.00000
yaxisval: 1.00000
degrees: 90.00000
Joystick Left position
xaxisval: -1.00000
yaxisval: 0.00000
degrees: 180.00000
Joystick North East position
xaxisval: 0.929412
yaxisval: 0.592157
degrees: 327.497528
You're passing in the arguments in the wrong order. std::atan2 expects the arguments in the order y,x, not x,y.
Yes, that is incredibly dumb, but it's related to how the tangent function is defined in the first place (which is defined as a ratio of the y-component to the x-component, not the other way around), and like many notational mistakes in mathematics, inertia set in thousands of years ago and you can't push back against it without being a crank.
So write your code like this:
float radians = atan2(1, 0);
Or, if you want everything as explicit as possible:
float x = 0, y = 1;
float radians = atan2(y, x); //Yes, that's the correct order, don't # me
And you'll get the results you expect.
Your second problem is that the values that atan2 correspond to don't match up with the directions you want. What you want is a circle where the top is 0°, the right side is 90°, the bottom is 180°, and the left side is 270°. Punching the values into atan2 is instead going to produce values where the right side is 0°, up is 90°, left is 180°, and down is 270°.
Also, in comparing with my own hardware, my y-axis is flipped compared to yours. Mine is y+↑, whereas your setup appears to be y-↑
So if you want to transform the normal atan2 rotation into the rotation you want, you'll need to transform it like this:
float radians = atan2(yaxisval, xaxisval);
float degrees = (radians * 180 / PI);
degrees = 90 - degrees;
if(degrees < 0)
degrees += 360;
Then, all you have to do from there is possibly transform the y-axis depending on whether you expect the joystick pushed up to return a positive or negative value. That's up to the domain of your program.
Related
I am currently trying to get the rotation of an object. I am using C++ and Bullet Physics. This is my code:
btScalar x, y, z;
body[0]->getCenterOfMassTransform().getBasis().getEulerZYX(z, y, x);
However, as I rotate the object around clockwise the number I get from the y (y is vertical in Bullet) axis goes from 0 to -90 to 0 to 90 and finally back to 0 for every quarter rotation. It is close but what I need is for it to go all the way from 0 to 360.
Bullet documentation says:
void getEulerZYX (btScalar &yaw, btScalar &pitch, btScalar &roll, unsigned int solution_number=1) const
and
solution_number Which solution of two possible solutions ( 1 or 2) are possible values
this is because euler angles are ambigous. have you tried solution 2?
I had the same problem. I using LibGDX with Bullet engine, so my code sample on Java, but I'm sure, that it will works on C++ too. Here is my solution (for Z axis):
body.getWorldTransform().getRotation(mRotation);
// That gives you an angle in all range but excluding (85, 95) and (-95, 85). For other axis you can try to get Pitch or Yaw.
float roll = mRotation.getRoll();
// That gives you an angle in range [0, 240). Clockwise and counterclockwise directions isn't detected.
float angle = mRotation.getAngleAround(0, 0, 1);
// Usually 0, but on (85, 95) and (-95, 85) becomes 1 and -1.
int gimbalPole = mRotation.getGimbalPole();
// Using roll (pitch/yaw for other axis) if it's defined, and using angle with gimble pole otherwise.
float rotation = (gimbalPole == 0) ? roll : angle * gimbalPole;
Obtained rotation will be in range (-180, 180). It can be easily converted to [0, 360) range:
if (rotation < 0) rotation += 360;
I'm trying to to rotate a sprite, let's say it's a gun, to a point, by taking the smallest route possible on a 360° (right or left depending on what side the point is closer to) and I'm having a few problems.
On a circle, it jumps from 359° to 0° so I can't direcly use my target angle - current angle.
Leep in mind that I'm using SFML so it's executing the function everyframe while isRotating is true.
This is the information that is available to me :
//The angle in degrees of my sprite
float currentAngle = pSprite.getRotation();
//The angle is needs to be once the rotation is over
float targetAngle = float(atan2(deltaY,deltaX) * 180 / (atan(1)*4) + 180);
I'm using a speed variable to increment or decrement the value of the angle every frame.
distance = Speed*Time.asSeconds();
currentAngle += distance;
First, get the difference:
diff = target - current
diff is either the "short" angle (the one resulting in a shorter rotation), or the "long" angle (rotation in the other direction which is longer). Notice that:
you never need to rotate for more than (as an absolute value) 180 degrees to get from one angle to another.
the "short" angle and the "long" angle have opposite signs (+/-) and their absolute values add to 360.
Example: 0 - 359 = -359. This is the "long" angle. We know this because its absolute value is > 180. The "short" angle will have the opposite sign and its absolute value will add up to 360 with 359, so it is 1.
An easy way to calculate this is:
while (diff < -180)
diff += 360;
while (diff > 180)
diff -= 360;
diff is now the angle you are looking for. So if it is negative, multiply your Speed by -1.
The while (as opposed to if) is there in case the angles are not in [0, 360] - for example, you have current = 1440, target = 359.
I'm using quaternions to rotate an object in OpenGL, but I'm having an issue where when I rotate from 5 degrees to -5 degrees (crossing a unit plane) I see my model spin "the long way." That is to say, it spins the full 350 degrees from 5 to -5 instead of the 10 degrees across the plane. This happens for rotations about each axis, anytime I "pass go" by rotating through 0 degrees, and I'm not sure how to fix it.
My code looks like this, where roll, pitch and yaw represent the Euler angles for my object's rotation:
quaternion[0] = cos(pitch/2)*cos(yaw/2)*cos(roll/2) + sin(pitch/2)*sin(yaw/2)*sin(roll/2);
quaternion[1] = sin(pitch/2)*cos(yaw/2)*cos(roll/2) - cos(pitch/2)*sin(yaw/2)*sin(roll/2);
quaternion[2] = cos(pitch/2)*sin(yaw/2)*cos(roll/2) + sin(pitch/2)*cos(yaw/2)*sin(roll/2);
quaternion[3] = cos(pitch/2)*cos(yaw/2)*sin(roll/2) - sin(pitch/2)*sin(yaw/2)*cos(roll/2);
glLoadIdentity();
glPushMatrix();
glRotatef((float) (2.0f * acos(quaternion[0]) * 180.0f / PI), quaternion[1], quaternion[2], quaternion[3]);
Any suggestions?
The problem are not your quaternions or the glRotate(), it is where you calculate the step distance for a rotation.
Possibly you are using a normalization of some kind, where a negative angle like -5 gets "corrected" to a value in the range 0..359, so it will become 355 and your rotation is then performed from 5 to 355.
This is how I calculate my line of sight vector and the up vector.
ly = sin(inclination);
lx = cos(inclination)*sin(azimuth);
lz = cos(inclination)*cos(azimuth);
uy = sin(inclination + M_PI / 2.0);
ux = cos(inclination + M_PI / 2.0)*sin(azimuth + M_PI);
uz = cos(inclination + M_PI / 2.0)*cos(azimuth + M_PI);
inclination is the angle of the line of sight vector from the xz plane and azimuth is the angle in the xz plane.
This works fine till my inclination reaches 225 degrees. At that point there is a discontinuity in the rotation for some reason. (Note By 225 degrees, I mean its past the upside-down point)
Any ideas as to why this is so?
EDIT: Never mind, figured it out. The azimuth does not need a 180 deg. tilt for the up vector.
I think you are talking of a limit angle of 90 degrees (pi). What you get is a normal behavior. When using gluLookAt, you specify an 'up' vector, used to determine the roll of the camera. In the special case where you are looking upside down, the 'up' vector is parallel to the eye direction vector, so it is not possible to determine the roll of the camera (this problem as an infinite number of solutions, so an arbitrary one is chosen by gluLookAt). May be you should compute this 'up' vector using your inclination and azimuth.
I'm not very good at math or geometry, but I want to draw some line segments at increasing angles. What I want to draw is something like when you hold your hand up and spread your fingers apart: lines that start at a common point and expand out at angles that have an equal difference between them.
I have tried this:
len = 300;
angle = 10;
for (i = 0; i <= 5; ++i) {
endPointX = 50 + len * Math.cos(angle);
endPointY = 50 + len * Math.tan(angle);
draw.Line(50, 50, endPointX, endPointY);
angle += 10;
}
However, that's totally wrong and produces something like this
http://i.stack.imgur.com/taX40.png
But I want something like this (bad mspaint, sorry):
http://i.stack.imgur.com/8xfpp.png
What's the right math for this?
There are two separate issues in your question, I will cover each.
Here's an ASCII picture of your situation:
B
+
/|
/ |
/ |
/ |
len / | y
/ |
/ |
/ |
/ __|
/ θ | |
+----------+
A x C
This is a right triangle. It has three sides:
The diagonal side in the picture opposite to the 90° angle is called the hypotenuse and has a length len. The hypotenuse is what you're trying to draw.
The vertical side is the side opposite to the angle θ and has a length y.
The horizontal side is the side adjacent to the angle θ and has a length x.
Given the above illustration the following equations are true:
cos(θ) = x/len
sin(θ) = y/len
These equations are another way of saying:
The cosine of an angle is equal to the length of the adjacent side divided by the length of the hypotenuse.
The sine of an angle is equal to the length of the opposite side divided by the length of the hypotenuse.
When solving the equation for x and y, you get:
x = len * cos(θ)
y = len * sin(θ)
So you want sin() and cos(), not cos() and tan(). If the point A is not at the origin, you would need to offset x and y by addition, like so:
x = len * cos(θ) + 50
y = len * sin(θ) + 50
With the values for x and y, you can find the coordinates for point B on the triangle, and thus be able to draw your lines.
Also, assuming you're programming in Java, the trigonometric functions in the Math class expect the angle in radians, not degrees. Lots of programming languages that provides trigonometric functions are like this.
Radians and degrees measure the same thing, but a complete rotation in degrees goes from 0 to 360° while a complete rotation in radians go from 0 to 2π.
To convert angles in degrees to radians, multiply the angle by π/180. In Java, the constant π is provided by Math.PI.
For example, an angle of 10° degrees is equivalent to 10 * π/180, or π/18 radians.
Firstly, you want cos and sin, not cos and tan.
Secondly, most maths libraries perform trigonometric functions in radians, not degrees. So 10 is a very large difference indeed! To convert from degrees to radians, multiply by (pi/180).
You shouldn't be using tan, but sin. If I remember correctly, it should be something like:
Math.cos(angle/180);
-Math.sin(angle/180);
The negative on sin is important.
The reason you are getting uneven looking angles is that every time you add 10 you're actually spinning the line around the circle 1.6 times.
The math functions expect angles to be in radians, not degrees.
360 degrees = 2*Math.PI radians.
Instead of 10, write "2*Math.PI/36.0"
Also, use sin instead of tan.