Maths Problem: Slow turn radius when following

BlitzMax Forums/BlitzMax Beginners Area/Maths Problem: Slow turn radius when following

Matt McFarland(Posted 2009) [#1]
Well in my game I currently have zombies that are following the player around. To follow the player I do this with each step
		d = ATan2(y - Player.y, x - Player.x) + 180
		xv = Cos(d) * speed
		yv = Sin(d) * speed


Now in my attempt to make the zombies follow at a slower rate:

		nextD = ATan2(y - Player.y, x - Player.x) + 180
		If d < nextd Then d:+1
		If d > nextd Then d:-1

I assumed that this would work, but unfortunately if the player is about 45 degrees(really I dont know how many degrees off) the zombie then turns the other direction and it shouldn't.

Any help would be much appreciated, thanks! Please keep in mind I don't have good knowledge of geometry, trigonometry, nor can I read algebra :(


Czar Flavius(Posted 2009) [#2]
Hm this is just a guess, but I think it might be because your code doesn't take into account the fact that angles are circular. For example, say a zombie is looking at 10 degrees, but the player is at 350 degrees. That's only 20 degrees to the left.. but your code will (I assume) make the zombie turn around "the long way", because it can't tell 350 is actually near 10 degrees.

Hm how to fix. A quick fix is this - do two comparisions, one for left and one for right. Take the difference between the angle, and then add 360 to the smallest angle, and take a second difference. Depending upon which difference is smallest, decide if to go left or right.

So in the above example, 350-10=340. Now add 360 to 10 to get 370. 370-350 = 20. 20 is smaller than 340, so we need to turn right. Or the opposite direction to normal! Note that I can't remember which way is which, so you might need to swap left and right in this post :D

There's probably a better way to do this, but I think this should do the trick.

I hope you're using Floats and not Ints ;D


Matt McFarland(Posted 2009) [#3]
hmm not sure how to do the two comparisons exactly? Is it a different ATan2 function?


Jesse(Posted 2009) [#4]
this is how I calculate my "chaser missile" on a shmup I am doing except I am using a couple other variables sense I am using fixed rate logic.
		Local TargetAngle# = ((ATan2(ny-y,nx-x))+360) Mod 360 ' keep the angle value positive
		Local difference# = TargetAngle-Direction ' Direction = current angle

		'turn toward target
		If Abs(difference) < 2 ' 2 degrees in error is considered target found.
			direction = targeAngle
		ElseIf Abs(difference) > 180
			If difference < 0 
				direction:+TurnSpeed
			ElseIf difference > 0 
				direction:-turnspeed
			EndIf 
		ElseIf Abs(difference) > 0
			If difference > 0
				direction:+TurnSpeed
			Else 
				direction :-Turnspeed
			EndIf
		EndIf
		Direction = (Direction+360) Mod 360 ' keep the angle positive.


turnspeed needs to be adjusted so it wont over shoot


BladeRunner(Posted 2009) [#5]
Function GetDir:Int(from:Int , target:Int) 
	'returns the factor to add to 'from' to get to 'target' in shortest way 
	If ( ( (from >= target) * ( (360 - from) + target) + (from < target) * ( (360 - target) + from) ) > ..
	( (from >= target) * (from - target) + (from < target) * (target - from) ) ) Then
		Return Sgn(target-from)
	Else
		Return Sgn(from-target)
	EndIf
End Function

This function returns the direction factor needed to get from 'from' to 'target'. Multiply the Factor with the amount of degrees to turn per frame.
Example
angle :+ GetDir(angle,target)*5 ' rotates 5° in the desired direction


Czar Flavius(Posted 2009) [#6]
nextD = ATan2(y - Player.y, x - Player.x) + 180
Local first_difference:Float = Abs(nextD - d)
If nextD < d Then nextD :+ 360 else d :+ 360
Local second_difference:Float = Abs(nextD - d)
If first_difference > second_difference Then d:+1 Else d:-1


The others' code is probably better, but just to illustrate what I was talking about.


Matt McFarland(Posted 2009) [#7]
Hmm..

Jesse your code they don't appear anymore, Czar and bladerunner, they follow the player at first but then start moving around in infinite circles.. Thanks guys, I'm trying to tweak your snippets and see if I can get them to work :S


Matt McFarland(Posted 2009) [#8]
BINGO: I just found the least expensive and most efficient way to code this, avoiding as much as trig as possible.

		Local dx:Float = Player.x - x
		Local dy:Float = Player.y - y
		Local sep:Float = Sqr(dx * dx + dy * dy)
		Local scale:Float = speed / sep
  		x:+dx * scale;
   		y:+dy * scale;
		d = ATan2(dY, dX)



Adapted from http://www.kongregate.com/forums/4/topics/44153#posts-990789


Matt McFarland(Posted 2009) [#9]
nevermind.. sigh.. they just turn just as fast...


Warpy(Posted 2009) [#10]
That's because you're scaling the distance, so the direction remains the same.

Does this make it any clearer:

This function returns the shortest difference between two angles.
Function andiff:Float(an1:Float,an2:Float)
	Return ((((an2-an1) Mod 360)+540) Mod 360)-180	'this one-liner is a lot of logic condensed into modular arithmetic
End Function


How the andiff function works:
(it will help if you draw some circles to follow along with this graphically as you read it)
First, take an2-an1. On a line, that would give you the distance between an1 and an2.
But we're working in a circle, so what if an2 = 359 and an1 = -2?
Then an2-an1 = 361, which is a full turn followed by turning one degree clockwise.
Obviously it would be quicker to just turn 1 degree clockwise.
So, you can take out all the full turns by taking (an2-an1) Mod 360.
(x Mod y gives you the remainder after dividing x by y, or, takes away as many multiples of y from x as it can)
(When dealing with angles, you can add or subtract any number of full turns from an angle as you like, and it will still represent the same direction)

So now we have a number between -360 and +360.

This still doesn't quite work, because what if an2 = 0 and an1 = 270?
We get ((an2-an1) Mod 360) = -270, or three quarters of a turn anti-clockwise, which is the same as a quarter of a turn clockwise, or +90 degrees.
So we need to add 360 degrees in this case.
Whenever (an2-an1) Mod 360 is less than -180, we need to add 360 degrees.

Another case is the other way around, if an2 = 270 and an1 = 0.
Then ((an2 - an1) Mod 360) = 270, so it would be quicker to turn -90 degrees.
So we need to subtract 360 degrees in this case.
Whenever (an2-an1) Mod 360 is greater than +180, we need to subtract 360 degrees.

Finally, if ((an2-an1) Mod 360 is in the range -180 .. +180, this is the shortest way of representing the angle, so we want it to stay the same.

Add a turn and a half, or 540 degrees.
This will represent a different direction because we've added an extra half a turn, but we'll take that off at the end.
So in the first (too low) case we would have a number between +180 and +360.
In the second (too high) case we would have a number between +720 and +900.
In the third (correct) case we would have a number between +360 and +720.

Now take the number Mod 360.
In the first case we get a number between +180 and +360 again.
In the second case it's between 0 and +180.
And in the final case it's between 0 and +360.

Finally, subtract the 180 degrees that we added on before to get back to the correct direction:
In the first case we get a number between 0 and +180.
In the second case we get a number between -180 and 0.
In the third case we get back to the original number between -180 and +180.

So no matter what an2 and an1 were, we have ended up with a number which represents the same amount of turning as an2-an1, but
is in the range -180 .. +180, so is the shortest distance round the circle from an1 to an2. That's exactly what we wanted!


Now, to put that into use:
Local dx:Float=Player.x - x
Local dy:Float=Player.y - y
Local an:Float=ATan2(dy,dx)	'absolute angle between zombie and player
Local diff:Float=andiff(d,an)	'work out the difference between the zombie's current direction and the direction towards the player

'you now have two options - either turn by some fraction of the angle difference, so
'you turn more quickly when the difference is bigger, or turn at a constant rate until
'you are facing the player

'Option 1: turn a fraction of the difference each frame
d:+diff*0.5

'Option 2: turn at a constant rate
Const turnspeed:Float=5	'define the maximum rate of turning, in degrees per frame
If Abs(diff)<turnspeed		'if the difference is less than the maximum speed, we can just set d=an
	d=an
Else
	d:+Sgn(diff)*turnspeed	'turn by turnspeed degrees in the correct direction
EndIf



Matt McFarland(Posted 2009) [#11]
Thanks Warpy, the code works great. I've haven't had much time to continue this project as of late, but that should change soon enough.