Reputation: 3341
I realize this is probaby the most noob question of all times, but my mind is completely stuck.
I have two angles, which represent the compass direction of two units, lets say
unit1: 90.0
unit2: 45.0
If these differ more than 20 degrees, unit2 needs to take unit1's direction, with +/-20 degrees, so that
unit1:90.0
unit2:70.0
I can figure out the difference in degrees between these two (signed) using
angle = 180 - abs(abs(unit1 - unit2) - 180)
but I need to know whether to adjust by +20 or -20.
For example, for this set:
unit1:270
unit2:350
unit2 needs to become 290 (adding 20 degrees to unit1)
I'm pretty sure there's probably a single python function which does this for me, but I am completely stuck and can't remember any of the math I learned 20 years ago.
I completely expect someone to make a fool out of me, but I would appreciate it anyways.
Upvotes: 1
Views: 11220
Reputation: 1
I know this is an old post but just wanted to share this nice little one-liner for comparing angles. Implementation uses numpy and leverages a bit of circle math. Only issue you might find is that it will return False for an absolute edge. e.g. unit1 = 90 and unit2 = 70. But will return True if unit2 were to = 70.01.
import math
import numpy
if math.cos(np.deg2rad(unit2) - np.deg2rad(unit1)) >= math.cos(np.deg2rad(20)):
unit2=unit1
Upvotes: 0
Reputation: 9796
Conceptually, you are looking for this pseudocode:
if angle_difference > 20:
if unit1 > unit2:
unit2 = unit1 - 20
else:
unit2 = unit1 + 20
That is, if, for example, unit1
is larger, then unit2
will increase until it's 20 less than unit1
. However, there is a trick I missed initially. If the angle difference is greater than 180, their complement is less than 180, so it's shorter for unit2
to approach unit1
from the other direction and the sign calculations switch.
def adjust_angle(a, b):
angle = abs(a - b)
if 340 > angle > 20:
adjustment = -20 if angle < 180 else 20
b = (a + adjustment if a > b else a - adjustment) % 360
return a, b
A few points to explain here. In the same way that if the angle difference is less than 20 you don't have to do anything, so is the case for greater than 340 (360-20).
The snippet
if 340 > angle > 20:
adjustment = -20 if angle < 180 else 20
b = (a + adjustment if a > b else a - adjustment) % 360
is equivalent in logic to the following
if 340 > angle > 20:
if angle < 180:
b = (a - 20 if a > b else a + 20) % 360
else:
b = (a + 20 if a > b else a - 20) % 360
By using the adjustment
variable, we simply eliminate code replication.
Finally, the modulo operation is used because sometimes in pure calculations unit2
may exceed the value 360. For example, for unit1 = 50
and unit2 = 340
, unit2
must increase by 50, effectively becoming 390. But since we're restricted from 0 to 360 degrees, the result will become 30.
Upvotes: 2
Reputation: 21453
The current way you are calculating the angle between the two directions will fall apart if the two directions are more then 360 apart:
unit1 = 360 * 4
unit2 = 50
angle = 180 - abs(abs(unit1 - unit2) - 180)
print(angle) #outputs -1050
I would highly recommend using modulus %
instead to calculate the angle:
#this will calculate the angle so that 0<=angle<360
angle = (unit2 - unit1) % 360
#this alone will give you the angle such that:
assert 0<= angle <360
assert (unit1 + angle) %360 == unit2 %360
although it would be easier to work with values between -180 and 180 instead of 0 to 360, so you can reduce the values above 180 into the negatives like so:
angle = (unit2 - unit1) % 360
if angle>180:
angle -=360
# or using @PM2Ring suggestion in comments
angle = 180 - (180 - unit2 + unit1) % 360
#now these two statements will always be True
assert -180< angle<=180
assert (unit1 + angle) %360 == unit2 %360
then you can just check for values over 20, or less then -20:
if angle>20:
unit2 = unit1 + 20
elif angle<-20:
unit2 = unit1 - 20
#else leave unit2 unchanged
edit: If you don't mind working with angles between 0 and 360 you can still do equivalent calculations using 340 as cutoff:
if 20<angle<=180:
unit2 = unit1 + 20
elif angle<340: #elif angle in range(181,340):
unit2 = unit1 - 20
#else: assert angle in range(21) or angle in range(340,360) #no change
This and the original post use 3 checks to accomplish the task but this method doesn't have a possible extra step of reducing angle
so this will execute unnoticeably faster but will probably be more confusing to work with.
Upvotes: 2
Reputation: 129
I can see why you got stuck on that. The other solution didn't really solve for cases like unit1 being at 30 degrees and unit2 being at 340, where it would rotate through the 360 to 0. The code can do with some cleaning up, but I hope this helps somewhat.
import math
# calculate and return the distance unit2
# needs to move to reach unit1
def get_distance(unit1, unit2):
phi = abs(unit2-unit1) % 360
sign = 1
# used to calculate sign
if not ((unit1-unit2 >= 0 and unit1-unit2 <= 180) or (
unit1-unit2 <= -180 and unit1-unit2 >= -360)):
sign = -1
if phi > 180:
result = 360-phi
else:
result = phi
return result*sign
# distance unit2 needs to move to reach unit1
print get_distance(90,45) # output 45
print get_distance(270, 350) # output -80
print get_distance(350, 30) # output -40 (unit2 moves -40 degrees)
print get_distance(30, 350) # output 40
unit1 = 30
unit2 = 350
# then calculate distance to move by taking 20 from distance
distance_to_move = (get_distance(unit1, unit2) - 20)
print (unit2 + distance_to_move)%360 # new position 10
I've changed it up a bit to work for more than 360 degrees as pointed out by Tadhg. The variance of 20 degrees is a bit less hard-coded too.
import math
# calculate and return the distance unit2
# needs to move to reach unit1
def get_distance(unit1, unit2):
phi = (unit2-unit1) % 360
sign = -1
# used to calculate sign
if not ((phi >= 0 and phi <= 180) or (
phi <= -180 and phi >= -360)):
sign = 1
if phi > 180:
result = 360-phi
else:
result = phi
return (result*sign), sign
def get_sign(unit1, unit2):
phi = (unit2-unit1) % 360
sign = 1
if ((phi >= 0 and phi <= 180) or (
phi <= -180 and phi >= -360)):
sign = -1
return sign
def new_position(unit1, unit2, variance = 20):
distance_to_move, sign = get_distance(unit1, unit2)
variance*=sign
# %360 to keep it within the circle
return (unit2+distance_to_move-variance)%360
# distance unit2 needs to move to reach unit1
assert (get_distance(90,45) == (45, 1))
assert (get_distance(270, 350) == (-80, -1))
assert (get_distance(350, 30) == (-40, -1))
assert (get_distance(30, 350) == (40, 1))
assert (get_distance(50, 360*4) == (50, 1))
assert (get_distance(360*4, 50) == (-50, -1))
print "----------"
assert (new_position(90,45) == 70)
assert (new_position(270, 350) == 290)
assert (new_position(350, 30) == 10)
assert (new_position(30, 350) == 10)
assert (new_position(50, 360*4) == 30)
assert (new_position(360*4, 50) == 20)
Upvotes: 6