Calculating if an angle is between two angles - math

So I am making a little game where I am checking if a character can "see" another where character A can see character B if A is within a certain distance of B, and the direction in degrees of A is +/- 45 degrees of the angle B is facing.
Currently, I do a little calculation where I'm checking if
(facingAngle - 45) =< angleOfTarget =< (facingAngle + 45)
This works fine except for when we cross the 360 degree line.
Let's say facingAngle = 359, angleOfTarget = 5. In this situation, the target is only 6 degrees off center, so I want my function to return true. Unfortunately, 5 is not between 314 and 404.

Just try
anglediff = (facingAngle - angleOfTarget + 180 + 360) % 360 - 180
if (anglediff <= 45 && anglediff>=-45) ....
The reason is that the difference in angles is facingAngle - angleOfTarget although due to wrapping effects, might be off by 360 degrees.
The add 180+360 then modulo 360 then subtract 180, effectively just converts everything to the range -180 to 180 degrees (by adding or subtracting 360 degrees).
Then you can check the angle difference easily, whether it is within -45 to 45 degrees.

Here is a simple function I found online, and modified. It works correctly for any angles (can be outside of 0-360). (This function is made to work in c, works in Xcode.)
Remember, it checks COUNTER-CLOCKWISE from angle A to angle B. It returns YES (true) if the angle is between :)
First, a simple conversion function to make all angles 1-360
//function to convert angle to 1-360 degrees
static inline double angle_1to360(double angle){
angle=((int)angle % 360) + (angle-trunc(angle)); //converts angle to range -360 + 360
if(angle>0.0)
return angle;
else
return angle + 360.0;
}
Check if angle is between :)
//check if angle is between angles
static inline BOOL angle_is_between_angles(float N,float a,float b) {
N = angle_1to360(N); //normalize angles to be 1-360 degrees
a = angle_1to360(a);
b = angle_1to360(b);
if (a < b)
return a <= N && N <= b;
return a <= N || N <= b;
}
Eg. To check if the angle 300 is between 180 and 10 degrees:
BOOL isBetween=angle_is_between_angles( 300, 180,10);
//RETURNS YES

There is a trigonometric solution that avoids the wrapping problem.
I'm assuming that you have (x, y) coordinates for both characters P1 and P2. You've already specified that you know the distance between the two which you presumably calculated using Pythagoras' theorem.
You can use the dot product of two vectors to calculate the angle between them:
A . B = |A| . |B| . cos(theta).
If you take A as the facingAngle vector it will be [cos(fA), sin(fA)], and will have magnitude |A| of 1.
If you take B as the vector between the two characters, and your distance above you get:
cos(theta) = (cos(fA) * (P2x - P1x) + sin(fA) * (P2y - P1y)) / |B|
where |B| is the distance you've already calculated.
You don't need to actually take the inverse cosine to find theta, since for range of -45 to +45 you just need to check for cos(theta) >= 0.70710678 (i.e. 1 / sqrt(2)).
This might seem slightly complicated, but the chances are that you've already got all of the required variables hanging around in your program anyway.

A simple solution to handle wrapping at the low end (into negative values), is just to add 360 to all your values:
(facingAngle + 315) =< (angleOfTarget + 360) =< (facingAngle + 405)
That way, the subtraction of 45 can never go negative, because it no longer happens.
To handle wrapping at the top end, you need to check again, adding another 360 to the angleOfTarget value:
canSee = (facingAngle + 315 <= angleOfTarget + 360) &&
(angleOfTarget + 360 <= facingAngle + 405);
canSee |= (facingAngle + 315 <= angleOfTarget + 720) &&
(angleOfTarget + 720 <= facingAngle + 405);

Another way using always minimum positive difference and comparing with threshold:
anglediff = Math.min(Math.abs(facingAngle - angleOfTarget), 360 - Math.abs(angleOfTarget - allowDirection));
if (anglediff <= 45)

Restating Alnitak's answer in a different way, a solution that avoids the angle wrap at 360 degrees is to restate the problem in a different coordinate system where the angles are always small. Here is the code:
def inside_angle(facing, target):
dot = cos(facing)*cos(target) + sin(facing)*sin(target)
angle = acos(dot)
return angle <= pi/4
This is done using vector projection. Assuming the vectors |facing> = [cos(facing) sin(facing)] and |target> = [cos(target) sin(target)], when projecting the target into the facing vector, the angle will range from zero, when the target is exactly at the facing vector or will increase to either side. This way we can just compare it to pi/4 (45 degrees). The formula for the angle is the following:
cos(angle) = <facing|target> / <target|target> <facing|facing>
That is, the cosine of the angle is the dot product between the vectors |facing> and |target> divided their modules, which is 1 in this case, which becomes:
angle = acos(<facing|target>)
Reference:
https://en.wikipedia.org/wiki/Vector_projection

Related

What is the mathematical way to determine an angle from two given true bearings?

Given that I have two true bearings as a start bearing 315 degrees and an end bearing 45 degrees, is there a better way to determine the angle between the two true bearings? The complication comes in when the start bearing is greater than the end bearing. I have the following that works but I figure there is a better/mathematical way.
double tStartBearing = 315;
double tEndBearing = 45;
double tAngle;
if (tStartBearing > tEndBearing) {
tAngle = tStartBearing - tEndBearing - 180;
} else {
tAngle = tEndBearing - tStartBearing;
}
Expect the resulting value of tAngle to be 90. Consider a start bearing of 0 and an end bearing of 359.9, the resulting value of tAngle should be 359.9, not 0.1.
The (signed) angle is always end - start. Assuming the start and end angles are both in the same range [n, n + 360), their difference will be between (-360, 360).
To normalize the difference to a positive angle in the range [0, 360), use:
tAngle = (tEndBearing - tStartBearing + 360) % 360;
To normalize the difference to a signed angle in the range [-180, 180), instead, use:
tAngle = (tEndBearing - tStartBearing + 360 + 180) % 360 - 180;
The above work whether the start angle is smaller than the end one, or the other way around.

Turn rate on a player's rotation doing a 360 once it hits pi

Making a game using Golang since it seems to work quite well for games. I made the player face the mouse always, but wanted a turn rate to make certain characters turn slower than others. Here is how it calculates the turn circle:
func (p *player) handleTurn(win pixelgl.Window, dt float64) {
mouseRad := math.Atan2(p.pos.Y-win.MousePosition().Y, win.MousePosition().X-p.pos.X) // the angle the player needs to turn to face the mouse
if mouseRad > p.rotateRad-(p.turnSpeed*dt) {
p.rotateRad += p.turnSpeed * dt
} else if mouseRad < p.rotateRad+(p.turnSpeed*dt) {
p.rotateRad -= p.turnSpeed * dt
}
}
The mouseRad being the radians for the turn to face the mouse, and I'm just adding the turn rate [in this case, 2].
What's happening is when the mouse reaches the left side and crosses the center y axis, the radian angle goes from -pi to pi or vice-versa. This causes the player to do a full 360.
What is a proper way to fix this? I've tried making the angle an absolute value and it only made it occur at pi and 0 [left and right side of the square at the center y axis].
I have attached a gif of the problem to give better visualization.
Basic summarization:
Player slowly rotates to follow mouse, but when the angle reaches pi, it changes polarity which causes the player to do a 360 [counts all the back to the opposite polarity angle].
Edit:
dt is delta time, just for proper frame-decoupled changes in movement obviously
p.rotateRad starts at 0 and is a float64.
Github repo temporarily: here
You need this library to build it! [go get it]
Note beforehand: I downloaded your example repo and applied my change on it, and it worked flawlessly. Here's a recording of it:
(for reference, GIF recorded with byzanz)
An easy and simple solution would be to not compare the angles (mouseRad and the changed p.rotateRad), but rather calculate and "normalize" the difference so it's in the range of -Pi..Pi. And then you can decide which way to turn based on the sign of the difference (negative or positive).
"Normalizing" an angle can be achieved by adding / subtracting 2*Pi until it falls in the -Pi..Pi range. Adding / subtracting 2*Pi won't change the angle, as 2*Pi is exactly a full circle.
This is a simple normalizer function:
func normalize(x float64) float64 {
for ; x < -math.Pi; x += 2 * math.Pi {
}
for ; x > math.Pi; x -= 2 * math.Pi {
}
return x
}
And use it in your handleTurn() like this:
func (p *player) handleTurn(win pixelglWindow, dt float64) {
// the angle the player needs to turn to face the mouse:
mouseRad := math.Atan2(p.pos.Y-win.MousePosition().Y,
win.MousePosition().X-p.pos.X)
if normalize(mouseRad-p.rotateRad-(p.turnSpeed*dt)) > 0 {
p.rotateRad += p.turnSpeed * dt
} else if normalize(mouseRad-p.rotateRad+(p.turnSpeed*dt)) < 0 {
p.rotateRad -= p.turnSpeed * dt
}
}
You can play with it in this working Go Playground demo.
Note that if you store your angles normalized (being in the range -Pi..Pi), the loops in the normalize() function will have at most 1 iteration, so that's gonna be really fast. Obviously you don't want to store angles like 100*Pi + 0.1 as that is identical to 0.1. normalize() would produce correct result with both of these input angles, while the loops in case of the former would have 50 iterations, in the case of the latter would have 0 iterations.
Also note that normalize() could be optimized for "big" angles by using floating operations analogue to integer division and remainder, but if you stick to normalized or "small" angles, this version is actually faster.
Preface: this answer assumes some knowledge of linear algebra, trigonometry, and rotations/transformations.
Your problem stems from the usage of rotation angles. Due to the discontinuous nature of the inverse trigonometric functions, it is quite difficult (if not outright impossible) to eliminate "jumps" in the value of the functions for relatively close inputs. Specifically, when x < 0, atan2(+0, x) = +pi (where +0 is a positive number very close to zero), but atan2(-0, x) = -pi. This is exactly why you experience the difference of 2 * pi which causes your problem.
Because of this, it is often better to work directly with vectors, rotation matrices and/or quaternions. They use angles as arguments to trigonometric functions, which are continuous and eliminate any discontinuities. In our case, spherical linear interpolation (slerp) should do the trick.
Since your code measures the angle formed by the relative position of the mouse to the absolute rotation of the object, our goal boils down to rotating the object such that the local axis (1, 0) (= (cos rotateRad, sin rotateRad) in world space) points towards the mouse. In effect, we have to rotate the object such that (cos p.rotateRad, sin p.rotateRad) equals (win.MousePosition().Y - p.pos.Y, win.MousePosition().X - p.pos.X).normalized.
How does slerp come into play here? Considering the above statement, we simply have to slerp geometrically from (cos p.rotateRad, sin p.rotateRad) (represented by current) to (win.MousePosition().Y - p.pos.Y, win.MousePosition().X - p.pos.X).normalized (represented by target) by an appropriate parameter which will be determined by the rotation speed.
Now that we have laid out the groundwork, we can move on to actually calculating the new rotation. According to the slerp formula,
slerp(p0, p1; t) = p0 * sin(A * (1-t)) / sin A + p1 * sin (A * t) / sin A
Where A is the angle between unit vectors p0 and p1, or cos A = dot(p0, p1).
In our case, p0 == current and p1 == target. The only thing that remains is the calculation of the parameter t, which can also be considered as the fraction of the angle to slerp through. Since we know that we are going to rotate by an angle p.turnSpeed * dt at every time step, t = p.turnSpeed * dt / A. After substituting the value of t, our slerp formula becomes
p0 * sin(A - p.turnSpeed * dt) / sin A + p1 * sin (p.turnSpeed * dt) / sin A
To avoid having to calculate A using acos, we can use the compound angle formula for sin to simplify this further. Note that the result of the slerp operation is stored in result.
result = p0 * (cos(p.turnSpeed * dt) - sin(p.turnSpeed * dt) * cos A / sin A) + p1 * sin(p.turnSpeed * dt) / sin A
We now have everything we need to calculate result. As noted before, cos A = dot(p0, p1). Similarly, sin A = abs(cross(p0, p1)), where cross(a, b) = a.X * b.Y - a.Y * b.X.
Now comes the problem of actually finding the rotation from result. Note that result = (cos newRotation, sin newRotation). There are two possibilities:
Directly calculate rotateRad by p.rotateRad = atan2(result.Y, result.X), or
If you have access to the 2D rotation matrix, simply replace the rotation matrix with the matrix
|result.X -result.Y|
|result.Y result.X|

problems with a simple coordinate conversion

This code is supposed to calculate and return the real part of a complex number with magnitude a and angle b in degrees. It gives me wrong numbers.
x = (a*(cos(b*(180/pi))));
This however, gives me the right numbers if the angle is given in radians.
x = (a*(cos(b)));
pi is defined as const double pi = 3.142
Any thoughts? I cannot see why the x should be wrong in the first but correct in the second example.
Since 180 degrees is 1 pi radian. The formula for degrees to radians should be
radian = (degree / 180) pi.
Thus the first formula should be
x = (a*(cos((b / 180)*pi))));
You have the conversion backwards: your formula changes b from radians to degrees before calculating its cosine. But you want to convert from degress to radians. The correct formula is
x = (a*(cos(b*(pi/180)));
though you could use fewer parentheses and use more spacing:
x = a * cos(b * pi / 180);
You are not using formula correctly
this can be written as:
x = (a*cos((b * pi)/180));

Inverse of math.atan2?

What is the inverse of the function
math.atan2
I use this in Lua where I can get the inverse of math.atan by math.tan.
But I am lost here.
EDIT
OK, let me give you more details.
I needed to calculate angle between 2 points (x1,y1) and (x2,y2) and I did,
local dy = y1-y2
local dx = x1-x2
local angle = atan2(dy,dx)* 180 / pi
Now if I have the angle, is it possible to get back dy and dx?
Given only the angle you can only derive a unit vector pointing to (dx, dy). To get the original (dx, dy) you also need to know the length of the vector (dx, dy), which I'll call len. You also have to convert the angle you derived from degrees back to radians and then use the trig equations mentioned elsewhere in this post. That is you have:
local dy = y1-y2
local dx = x1-x2
local angle = atan2(dy,dx) * 180 / pi
local len = sqrt(dx*dx + dy*dy)
Given angle (in degrees) and the vector length, len, you can derive dx and dy by:
local theta = angle * pi / 180
local dx = len * cos(theta)
local dy = len * sin(theta)
Apparently, something like this will help:
x = cos(theta)
y = sin(theta)
Simple Google search threw this up, and the guy who asked the question said it solved it.
You'll probably get the wrong numbers if you use:
local dy = y1-y2
local dx = x1-x2
local angle = atan2(dy,dx) * 180 / pi
If you are using the coordinate system where y gets bigger going down the screen and x gets bigger going to the right then you should use:
local dy = y1 - y2
local dx = x2 - x1
local angle = math.deg(math.atan2(dy, dx))
if (angle < 0) then
angle = 360 + angle
end
The reason you want to use this is because atan2 in lua will give you a number between -180 and 180. It will be correct until you hit 180 then as it should go beyond 180 (i.e. 187) it will invert it to a negative number going down from -180 to 0 as you get closer to 360. To correct for this we check to see if the angle is less than 0 and if it is we add 360 to give us the correct angle.
According this reference:
Returns the arc tangent of y/x (in radians), but uses the signs of
both parameters to find the quadrant of the result. (It also handles
correctly the case of x being zero.)
So I guess you can use math.tan to invert it also.
As atan2 works as tan-1, so the inverse could be tan, taking into consideration conversion between radian and degree

comparing two angles

Given four points in the plane, A,B,X,Y, I wish to determine which of the following two angles is smaller ∢ABX or ∢ABY.
The angle ∢ABX is defined as the angle of BX, when AB is translated to lie on the open segment (-∞,0]. Intuitively when saying ∢ABX I mean the angle you get when you turn left after visiting vertex B.
I'd rather not use cos or sqrt, in order to preserve accuracy, and to minimize performance (the code would run on an embedded system).
In the case where A=(-1,0),B=(0,0), I can compare the two angles ∢ABX and ∢ABY, by calculating the dot product of the vectors X,Y, and watch its sign.
What I can do in this case is:
Determine whether or not ABX turns right or left
If ABX turns left check whether or not Y and A are on the same side of the line on segment BX. If they are - ∢ABX is a smaller than ABY.
If ABX turns right, then Y and A on the same side of BX means that ∢ABX is larger than ∢ABY.
But this seems too complicated to me.
Any simpler approach?
Here's some pseudocode. Doesn't detect the case when both angles are the same. Also doesn't deal with angle orientation, e.g. assumes all angles are <= 180 degrees.
v0 = A-B
v1 = X-B
v2 = Y-B
dot1 = dot(v0, v1)
dot2 = dot(v0, v2)
if(dot1 > 0)
if(dot2 < 0)
// ABX is smaller
if(dot1 * dot1 / dot(v1,v1) > dot2 * dot2 / dot(v2, v2) )
// ABX is smaller
// ABY is smaller
if(dot2 > 0)
// ABY is smaller
if(dot1 * dot1 / dot(v1,v1) > dot2 * dot2 / dot(v2,v2) )
// ABY is smaller
// ABX is smaller
Note that much of this agonizing pain goes away if you allow taking two square roots.
Center the origin on B by doing
X = X - B
Y = Y - B
A = A - B
EDIT: you also need to normalise the 3 vectors
A = A / |A|
X = X / |X|
Y = Y / |Y|
Find the two angles by doing
acos(A dot X)
acos(A dot Y)
===
I don't understand the point of the loss of precision. You are just comparing, not modifying in any way the coordinates of the points...
You might want to check out Rational Trigonometry. The ideas of distance and angle are replaced by quadrance and spread, which don't involve sqrt and cos. See the bottom of that webpage to see how spread between two lines is calculated. The subject has its own website and even a youtube channel.
I'd rather not use cos or sqrt, in order to preserve accuracy.
This makes no sense whatsoever.
But this seems too complicated to me.
This seems utterly wrong headed to me.
Take the difference between two vectors and look at the signs of the components.
The thing you'll have to be careful about is what "smaller" means. That idea isn't very precise as stated. For example, if one point A is in quadrant 4 (x-component > 0 and y-component < 0) and the other point B is in quadrant 1 (x-component > 0 and y-component > 0), what does "smaller" mean? The angle of the vector from the origin to A is between zero and π/2; the angle of the vector from the origin to B is between 3π/4 and 2π. Which one is "smaller"?
I am not sure if you can get away without using sqrt.
Simple:
AB = A-B/|A-B|
XB = X-B/|X-B|
YB = Y-B/|Y-B|
if(dot(XB,AB) > dot (YB,AB)){
//<ABY is grater
}
else
{
...
}
Use the law of cosines: a**2 + b**2 - 2*a*b*cos(phi) = c**2
where a = |ax|, b =|bx| (|by|), c=|ab| (|ay|) and phi is your angle ABX (ABY)

Resources