How to map atan2() to degrees 0-360 - math

atan2(y, x) has that discontinuity at 180° where it switches to -180°..0° going clockwise.
How do I map the range of values to 0°..360°?
here is my code:
CGSize deltaPoint = CGSizeMake(endPoint.x - startPoint.x, endPoint.y - startPoint.y);
float swipeBearing = atan2f(deltaPoint.height, deltaPoint.width);
I'm calculating the direction of a swiping touch event given the startPoint and endPoint, both XY point structs. The code is for the iPhone but any language that supports atan2f() will do.

Solution using Modulo
A simple solution that catches all cases.
degrees = (degrees + 360) % 360; // +360 for implementations where mod returns negative numbers
Explanation
Positive: 1 to 180
If you mod any positive number between 1 and 180 by 360, you will get the exact same number you put in. Mod here just ensures these positive numbers are returned as the same value.
Negative: -180 to -1
Using mod here will return values in the range of 180 and 359 degrees.
Special cases: 0 and 360
Using mod means that 0 is returned, making this a safe 0-359 degrees solution.

(x > 0 ? x : (2*PI + x)) * 360 / (2*PI)

Add 360° if the answer from atan2 is less than 0°.

Or if you don't like branching, negate the two parameters and add 180° to the answer.
(Adding 180° to the return value puts it nicely in the 0-360 range, but flips the angle. Negating both input parameters flips it back.)

#erikkallen is close but not quite right.
theta_rad = atan2(y,x);
theta_deg = (theta_rad/M_PI*180) + (theta_rad > 0 ? 0 : 360);
This should work in C++: (depending on how fmod is implemented, it may be faster or slower than the conditional expression)
theta_deg = fmod(atan2(y,x)/M_PI*180,360);
Alternatively you could do this:
theta_deg = atan2(-y,-x)/M_PI*180 + 180;
since (x,y) and (-x,-y) differ in angles by 180 degrees.

I have 2 solutions that seem to work for all combinations of positive and negative x and y.
1) Abuse atan2()
According to the docs atan2 takes parameters y and x in that order. However if you reverse them you can do the following:
double radians = std::atan2(x, y);
double degrees = radians * 180 / M_PI;
if (radians < 0)
{
degrees += 360;
}
2) Use atan2() correctly and convert afterwards
double degrees = std::atan2(y, x) * 180 / M_PI;
if (degrees > 90)
{
degrees = 450 - degrees;
}
else
{
degrees = 90 - degrees;
}

#Jason S: your "fmod" variant will not work on a standards-compliant implementation. The C standard is explicit and clear (7.12.10.1, "the fmod functions"):
if y is nonzero, the result has the same sign as x
thus,
fmod(atan2(y,x)/M_PI*180,360)
is actually just a verbose rewriting of:
atan2(y,x)/M_PI*180
Your third suggestion, however, is spot on.

Here's some javascript. Just input x and y values.
var angle = (Math.atan2(x,y) * (180/Math.PI) + 360) % 360;

This is what I normally do:
float rads = atan2(y, x);
if (y < 0) rads = M_PI*2.f + rads;
float degrees = rads*180.f/M_PI;

An alternative solution is to use the mod () function defined as:
function mod(a, b) {return a - Math.floor (a / b) * b;}
Then, with the following function, the angle between ini(x,y) and end(x,y) points is obtained. The angle is expressed in degrees normalized to [0, 360] deg. and North referencing 360 deg.
function angleInDegrees(ini, end) {
var radian = Math.atan2((end.y - ini.y), (end.x - ini.x));//radian [-PI,PI]
return mod(radian * 180 / Math.PI + 90, 360);
}

angle = Math.atan2(x,y)*180/Math.PI;
I have made a Formula for orienting angle into 0 to 360
angle + Math.ceil( -angle / 360 ) * 360;

double degree = fmodf((atan2(x, y) * (180.0 / M_PI)) + 360, 360);
This will return degree from 0°-360° counter-clockwise, 0° is at 3 o'clock.

A formula to have the range of values from 0 to 360 degrees.
f(x,y)=180-90*(1+sign(x))* (1-sign(y^2))-45*(2+sign(x))*sign(y)
-(180/pi())*sign(x*y)*atan((abs(x)-abs(y))/(abs(x)+abs(y)))

The R packages geosphere will calculate bearingRhumb, which is a constant bearing line given an origin point and easting/northing. The easting and northing must be in a matrix or vector. The origin point for a wind rose is 0,0. The following code seems to readily resolve the issue:
windE<-wind$uasE
windN<-wind$vasN
wind_matrix<-cbind(windE, windN)
wind$wind_dir<-bearingRhumb(c(0,0), wind_matrix)
wind$wind_dir<-round(wind$wind_dir, 0)

theta_rad = Math.Atan2(y,x);
if(theta_rad < 0)
theta_rad = theta_rad + 2 * Math.PI; //if neg., add 2 PI to it
theta_deg = (theta_rad/M_PI*180) ; //convert from radian to degree
//or
theta_rad = Math.Atan2(y,x);
theta_rad = (theta_rad < 0) ? theta_rad + 2 * Math.PI : theta_rad;
theta_deg = (theta_rad/M_PI*180) ;
-1 deg becomes (-1 + 360) = 359 deg
-179 deg becomes (-179 + 360) = 181 deg

For your application I suspect you don't need exact degrees and would prefer a more approximate compass angle, eg 1 of 16 directions? If so then this code avoids atan issues and indeed avoids floating point altogether. It was written for a video game so uses 8 bit and 16 bit integers:
/*
349.75d 11.25d, tan=0.2034523
\ /
\ Sector /
\ 0 / 22.5d tan = ?2 - 1
15 | 1 33.75
| / 45d, tan = 1
14 | 2 _56.25
| / 67.5d, tan = 1 + ?2
13 | 3
| __ 78.75
|
12---------------+----------------4 90d tan = infty
| __ 101.25
|
11 | 5
|
10 | 6
|
9 | 7
8
*/
// use signs to map sectors:
static const int8_t map[4][5] = { /* +n means n >= 0, -n means n < 0 */
/* 0: +x +y */ {0, 1, 2, 3, 4},
/* 1: +x -y */ {8, 7, 6, 5, 4},
/* 2: -x +y */ {0, 15, 14, 13, 12},
/* 3: -x -y */ {8, 9, 10, 11, 12}
};
int8_t sector(int8_t x, int8_t y) { // x,y signed in range -128:127, result 0:15 from north, clockwise.
int16_t tangent; // 16 bits
int8_t quadrant = 0;
if (x > 0) x = -x; else quadrant |= 2; // make both negative avoids issue with negating -128
if (y > 0) y = -y; else quadrant |= 1;
if (y != 0) {
// The primary cost of this algorithm is five 16-bit multiplies.
tangent = (int16_t)x*32; // worst case y = 1, tangent = 255*32 so fits in 2 bytes.
/*
determine base sector using abs(x)/abs(y).
in segment:
0 if 0 <= x/y < tan 11.25 -- centered around 0 N
1 if tan 11.25 <= x/y < tan 33.75 -- 22.5 NxNE
2 if tan 33.75 <= x/y < tan 56.25 -- 45 NE
3 if tan 56.25 <= x/y < tan 78.75 -- 67.5 ExNE
4 if tan 78.75 <= x/y < tan 90 -- 90 E
*/
if (tangent > y*6 ) return map[quadrant][0]; // tan(11.25)*32
if (tangent > y*21 ) return map[quadrant][1]; // tan(33.75)*32
if (tangent > y*47 ) return map[quadrant][2]; // tan(56.25)*32
if (tangent > y*160) return map[quadrant][3]; // tan(78.75)*32
// last case is the potentially infinite tan(90) but we don't need to check that limit.
}
return map[quadrant][4];
}

Related

Simplex noise magic numbers explanation

I was reading The Book of Shader's chapter about simplex noise (click for full code), and had difficulty understanding a few magic numbers used here. This will not be a bug related thread, but should make sense under SO's community criteria.
See these lines:
vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
+ i.x + vec3(0.0, i1.x, 1.0 ));
// random numbers for gradient generation
// element wise: 0.5 - x ^ 4
// use max clamp element wise: if x < 0 then m = 0 (i.e. the gradient from the vertex is 0)
// x1, x2, x3: 3 verteces of triangle simplex
// dot product is the distance from v to simpelx verteces
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
m = m*m ;
m = m*m ;
vec3 x = 2.0 * fract(p * C.www) - 1.0; // gradient? 2 * fract(x / 41) - 1, in [-1, 1]
vec3 h = abs(x) - 0.5; // in [-0.5, 0.5]
vec3 ox = floor(x + 0.5); // in [-1, 1]
vec3 a0 = x - ox;
// (x - ox) ^ 2 + (abs(x) - .5) ^ 2
m *= 1.79284291400159 - 0.85373472095314 * ( a0 * a0 + h * h ); // ???
Meanwhile I understand in another java implementation they used a precomputed 2d array table and a random index to look up gradients, the above lines don't make much sense for me. I guess m stands for weights for each vertex's gradient contribution, and the rest remains a puzzle.
Hope there could be resources / comments help me out on understanding this snippet.

Calculate position of a point on an arc

I'm building a gauge. I took off from an example that was a half circle, see from the image:
To transform percentage into the angle the original chart had these three functions:
percToDeg: function (perc) {
return perc * 360;
},
percToRad: function (perc) {
return this.degToRad(this.percToDeg(perc));
},
degToRad: function (deg) {
return deg * Math.PI / 180;
},
Now this all looks and works great, however I wanted to adjust the gauge so that the arc extends another 45 degrees in both directions, see this:
However now percToDeg function doesn't work anymore. Can you please help me figure out a function that for a given percentage places the point (tip of the needle in my case) of on the arc correctly - 0% should be 225 degrees, 50% 90 degrees and 100% -45 degrees?
Thanks
You want to use something called linear interpolation, with your starting range being [0, 100] and your destination range being [225, -45].
The general equation for this, for an x in range [a, b] to y in range [c, d]
y = c + (x - a) * (d - c) / (b - a)
In your case,
a = 0
b = 100
c = 225
d = -45
For example, if you want to find where 50 maps to in the range:
y = 225 + (50 - 0) * (-45 - 225) / (100 - 0)
y = 225 + 50 * -270 / 100
y = 225 - 135
y = 90

Determining the average angle

I'm developing an application that involves getting the camera angle in a game. The angle can be anywhere from 0-359. 0 is North, 90 is East, 180 is South, etc. I'm using an API, which has a getAngle() method in Camera class.
How would I find the average between different camera angles. The real average of 0 and 359 is 179.5. As a camera angle, that would be South, but obviously 0 and 359 are both very close to North.
You can think of it in terms of vectors. Let θ1 and θ2 be your two angles expressed in radians. Then we can determine the x and y components of the unit vectors that are at these angles:
x1 = sin(θ1)
y1 = cos(θ1)
x2 = sin(θ2)
y2 = cos(θ2)
You can then add these two vectors, and determine the x and y components of the result:
x* = x1 + x2
y* = y1 + y2
Finally, you can determine the angle of this resulting vector:
θavg = tan-1(y*/x*)
or, even better, use atan2 (a function supported by many languages):
θavg = atan2(y*, x*)
You will probably have to separately handle the cases where y* = 0 and x* = 0, since this means the two vectors are pointing in exactly opposite directions (so what should the 'average' be?).
It depends what you mean by "average". But the normal definition is the bisector of the included acute angle. You must put both within 180 degrees of each other. There are many ways to do this, but a simple one is to increment or decrement one of the angles. If the angles are a and b, then this will do it:
if (a < b)
while (abs(a - b) > 180) a = a + 360
else
while (abs(a - b) > 180) a = a - 360
Now you can compute the simple average:
avg = (a + b) / 2
Of course you may want to normalize one more time:
while (avg < 0) avg = avg + 360
while (avg >= 360) avg = avg - 360
On your example, you'd have a=0, b=359. The first loop would increment a to 360. The average would be 359.5. Of course you could round that to an integer if you like. If you round up to 360, then the final set of loops will decrement to 0.
Note that if your angles are always normalized to [0..360) none of these loops ever execute more than once. But they're probably good practice so that a wild argument doesn't cause your code to fail.
You want to bisect the angles not average them. First get the distance between them, taking the shortest way around, then divide that in half and add to one of the angles. Eg:
A = 355
B = 5
if (abs(A - B) < 180) {
Distance = abs(A - B)
if (A < B) {
Bisect = A + Distance / 2
}
else {
Bisect = B + Distance / 2
}
}
else {
Distance = 360 - abs(A - B)
if (A < B) {
Bisect = A - Distance / 2
}
else {
Bisect = B - Distance / 2
}
}
Or something like that -- "Bisect" should come out to zero for the given inputs. There are probably clever ways to make the arithmetic come out with fewer if and abs operations.
In a comment, you mentioned that all "angles" to be averaged are within 90 degrees to each other. I am guessing that there is really only one camera, but it moves around a lot, and you are creating some sort of picture stability mechanism for the camera POV.
In any case, there is only the special case where the camera may be in the 270-359 quadrant and the 0-89 quadrant. For all other cases, you can just take a simple average. So, you just need to detect that special case, and when it happens, treat the angles in the 270-359 quadrant as -90 to -1 instead. Then, after computing the simple average, adjust it back into the 270-359 quadrant if necessary.
In C code:
int quadrant (int a) {
assert(0 <= a && a < 360);
return a/90;
}
double avg_rays (int rays[], int num) {
int i;
int quads[4] = { 0, 0, 0, 0 };
double sum = 0;
/* trivial case */
if (num == 1) return rays[0];
for (i = 0; i < num; ++i) ++quads[quadrant(rays[i])];
if (quads[0] == 0 || quads[3] == 0) {
/* simple case */
for (i = 0; i < num; ++i) sum += rays[i];
return sum/num;
}
/* special case */
for (i = 0; i < num; ++i) {
if (quadrant(rays[i]) == 3) rays[i] -= 360;
sum += rays[i];
}
return sum/num + (sum < 0) * 360;
}
This code can be optimized at the expense of clarity of purpose. When you detect the special case condition, you can fix up the sum after the fact. So, you can compute sum and figure out the special case and do the fix up in a single pass.
double avg_rays_opt (int rays[], int num) {
int i;
int quads[4] = { 0, 0, 0, 0 };
double sum = 0;
/* trivial case */
if (num == 1) return rays[0];
for (i = 0; i < num; ++i) {
++quads[quadrant(rays[i])];
sum += rays[i];
}
if (quads[0] == 0 || quads[3] == 0) {
/* simple case */
return sum/num;
}
/* special case */
sum -= quads[3]*360;
return sum/num + (sum < 0) * 360;
}
I am sure it can be further optimized, but it should give you a start.

Given two points on a circle relative to their degrees, what is the degrees between them?

Note: I'm using Lua.
So, I'm trying to find out the degrees between two points on a circle. The problem is between something like 340 and 20, where the correct answer is 40 degrees, but doing something like
function FindLeastDegrees(s, f)
return ((f - s+ 360) % 360)
end
print(FindLeastDegrees(60, 260))
-- S = Start, F = Finish (In degrees)
Which works all all situations except for when trying to figure out the distance between the two. This below code is my next failed attempt.
function FindLeastDegrees(s, f)
local x = 0
if math.abs(s-f) <= 180 then
x = math.abs(s-f)
else
x = math.abs(f-s)
end
return x
end
print(FindLeastDegrees(60, 260))
I then tried:
function FindLeastDegrees(s, f)
s = ((s % 360) >= 0) and (s % 360) or 360 - (s % 360);
f = ((f % 360) >= 0) and (f % 360) or 360 - (f % 360);
return math.abs(s - f)
end
print(FindLeastDegrees(60, 350))
--> 290 (Should be 70)
So that failed. :/
So how would you find the shortest amount of degrees between two other degrees, and then if you should go clockwise or counterclockwise (Add or subtract) to get there. I'm entirely confused.
A few examples of what I'm trying to do...
FindLeastDegrees(60, 350)
--> 70
FindLeastDegrees(-360, 10)
--> 10
Which seems so hard! I know I will have to use...
Modulus
Absolute Values?
I would also like it to return if I should add or subtract to get to the value 'Finish'.
Sorry for the lengthy description, I think you probably have got it.... :/
If the degrees are in the 0 to 360 range, the % 360 part can be skipped:
function FindLeastDegrees(s, f)
diff = math.abs(f-s) % 360 ;
return math.min( 360-diff, diff )
end

Computing the 3D coordinates on a unit sphere from a 2D point

I have a square bitmap of a circle and I want to compute the normals of all the pixels in that circle as if it were a sphere of radius 1:
The sphere/circle is centered in the bitmap.
What is the equation for this?
Don't know much about how people program 3D stuff, so I'll just give the pure math and hope it's useful.
Sphere of radius 1, centered on origin, is the set of points satisfying:
x2 + y2 + z2 = 1
We want the 3D coordinates of a point on the sphere where x and y are known. So, just solve for z:
z = ±sqrt(1 - x2 - y2).
Now, let us consider a unit vector pointing outward from the sphere. It's a unit sphere, so we can just use the vector from the origin to (x, y, z), which is, of course, <x, y, z>.
Now we want the equation of a plane tangent to the sphere at (x, y, z), but this will be using its own x, y, and z variables, so instead I'll make it tangent to the sphere at (x0, y0, z0). This is simply:
x0x + y0y + z0z = 1
Hope this helps.
(OP):
you mean something like:
const int R = 31, SZ = power_of_two(R*2);
std::vector<vec4_t> p;
for(int y=0; y<SZ; y++) {
for(int x=0; x<SZ; x++) {
const float rx = (float)(x-R)/R, ry = (float)(y-R)/R;
if(rx*rx+ry*ry > 1) { // outside sphere
p.push_back(vec4_t(0,0,0,0));
} else {
vec3_t normal(rx,sqrt(1.-rx*rx-ry*ry),ry);
p.push_back(vec4_t(normal,1));
}
}
}
It does make a nice spherical shading-like shading if I treat the normals as colours and blit it; is it right?
(TZ)
Sorry, I'm not familiar with those aspects of C++. Haven't used the language very much, nor recently.
This formula is often used for "fake-envmapping" effect.
double x = 2.0 * pixel_x / bitmap_size - 1.0;
double y = 2.0 * pixel_y / bitmap_size - 1.0;
double r2 = x*x + y*y;
if (r2 < 1)
{
// Inside the circle
double z = sqrt(1 - r2);
.. here the normal is (x, y, z) ...
}
Obviously you're limited to assuming all the points are on one half of the sphere or similar, because of the missing dimension. Past that, it's pretty simple.
The middle of the circle has a normal facing precisely in or out, perpendicular to the plane the circle is drawn on.
Each point on the edge of the circle is facing away from the middle, and thus you can calculate the normal for that.
For any point between the middle and the edge, you use the distance from the middle, and some simple trig (which eludes me at the moment). A lerp is roughly accurate at some points, but not quite what you need, since it's a curve. Simple curve though, and you know the beginning and end values, so figuring them out should only take a simple equation.
I think I get what you're trying to do: generate a grid of depth data for an image. Sort of like ray-tracing a sphere.
In that case, you want a Ray-Sphere Intersection test:
http://www.siggraph.org/education/materials/HyperGraph/raytrace/rtinter1.htm
Your rays will be simple perpendicular rays, based off your U/V coordinates (times two, since your sphere has a diameter of 2). This will give you the front-facing points on the sphere.
From there, calculate normals as below (point - origin, the radius is already 1 unit).
Ripped off from the link above:
You have to combine two equations:
Ray: R(t) = R0 + t * Rd , t > 0 with R0 = [X0, Y0, Z0] and Rd = [Xd, Yd, Zd]
Sphere: S = the set of points[xs, ys, zs], where (xs - xc)2 + (ys - yc)2 + (zs - zc)2 = Sr2
To do this, calculate your ray (x * pixel / width, y * pixel / width, z: 1), then:
A = Xd^2 + Yd^2 + Zd^2
B = 2 * (Xd * (X0 - Xc) + Yd * (Y0 - Yc) + Zd * (Z0 - Zc))
C = (X0 - Xc)^2 + (Y0 - Yc)^2 + (Z0 - Zc)^2 - Sr^2
Plug into quadratic equation:
t0, t1 = (- B + (B^2 - 4*C)^1/2) / 2
Check discriminant (B^2 - 4*C), and if real root, the intersection is:
Ri = [xi, yi, zi] = [x0 + xd * ti , y0 + yd * ti, z0 + zd * ti]
And the surface normal is:
SN = [(xi - xc)/Sr, (yi - yc)/Sr, (zi - zc)/Sr]
Boiling it all down:
So, since we're talking unit values, and rays that point straight at Z (no x or y component), we can boil down these equations greatly:
Ray:
X0 = 2 * pixelX / width
Y0 = 2 * pixelY / height
Z0 = 0
Xd = 0
Yd = 0
Zd = 1
Sphere:
Xc = 1
Yc = 1
Zc = 1
Factors:
A = 1 (unit ray)
B
= 2 * (0 + 0 + (0 - 1))
= -2 (no x/y component)
C
= (X0 - 1) ^ 2 + (Y0 - 1) ^ 2 + (0 - 1) ^ 2 - 1
= (X0 - 1) ^ 2 + (Y0 - 1) ^ 2
Discriminant
= (-2) ^ 2 - 4 * 1 * C
= 4 - 4 * C
From here:
If discriminant < 0:
Z = ?, Normal = ?
Else:
t = (2 + (discriminant) ^ 1 / 2) / 2
If t < 0 (hopefully never or always the case)
t = -t
Then:
Z: t
Nx: Xi - 1
Ny: Yi - 1
Nz: t - 1
Boiled farther still:
Intuitively it looks like C (X^2 + Y^2) and the square-root are the most prominent figures here. If I had a better recollection of my math (in particular, transformations on exponents of sums), then I'd bet I could derive this down to what Tom Zych gave you. Since I can't, I'll just leave it as above.

Resources