Homing Missile PN angle problem

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By equus

Hello! I’m trying to implement a Proportional Navigation (https://en.wikipedia.org/wiki/Proportional_navigation) and I found this formula:

Required Acceleration = LOS * LOS_Rate * NC + APN_bias

at this link https://gamedev.stackexchange.com/questions/27923/implementing-simple-proportional-navigation-for-a-homing-missile/27983

I want to create a homing missile in 2D but it is not working as I expected and also I don’t know the formula for 2D.
I don’t need to use precisely the same formula as above. I just want to create a homing missile that moves elliptical and misses the target if it moves.

Here is the code:

export(float) var speed = 600
export(float) var navigation_const = 3
var _time_to_use_pn = 0.5
var current_velocity = Vector2()
var enemy_target
var _last_los = 0

func set_proportional_navigation(delta):

	_time_to_use_pn -= delta
	if _time_to_use_pn > 0:
	
		var cur_rot = get_global_rot()
		var forward_dir = Vector2(sin(cur_rot), cos(cur_rot))
		current_velocity = forward_dir.normalized() * speed * (-1)
		
		return
	
	if enemy_target != null:
		var my_pos = get_global_pos()
		var target_pos = enemy_target.get_global_pos()
		var cur_rot = get_global_rot()

		var los = atan2(my_pos.y - target_pos.y, my_pos.x - target_pos.x)
		var los_rate = los - _last_los
		_last_los = los

		var angle = los_rate * navigation_const

		rotate(angle)

		var forward_dir = Vector2(sin(cur_rot), cos(cur_rot))
		current_velocity = forward_dir.normalized() * speed * (-1)

Edit: I’m using Godot 2.1.4.
Edit 2: The code above contains another formula for 2d.

Edit 3: I found this link: https://gamemechanicexplorer.com/#homingmissiles-1, and I tried to translate the code to Godot but it doesn’t work. My code:

var my_pos = parent_weapon.get_global_pos()
var target_pos = enemy_target.get_global_pos()
var cur_rot = parent_weapon.get_global_rot()

var rotation_vel = deg2rad(5)

var angle = angle_between(target_pos, my_pos)

# Gradually (rotation_vel) aim the missile towards the target angle
if cur_rot != angle:
	# Calculate difference between the current angle and targetAngle
	var delta = angle - cur_rot
	
	# Keep it in range from -180 to 180 to make the most efficient turns.
	if delta > PI: delta -= PI * 2
	if delta < -PI: delta += PI * 2
	
	if delta > 0:
		# Turn clockwise
		cur_rot += rotation_vel
	else:
		# Turn counter-clockwise
		cur_rot -= rotation_vel
	
	# Just set angle to target angle if they are close
	if abs(delta) < rotation_vel:
			cur_rot = angle

parent_weapon.set_global_rot(cur_rot)

var forward_dir = Vector2(sin(cur_rot), cos(cur_rot))
current_velocity = forward_dir.normalized() * parent_weapon.w_speed

missile_shot.parent_weapon.global_translate(missile_shot.current_velocity * p_delta)

The maths in the linked articles is annoyingly wrong.

For example in the stackoverflow-answer it says Required Acceleration = LOS * LOS_Rate * NC + APN_bias and APN_bias = LOS_Rate/delta_T * ( NC/2 ). So since LOS is a vector and all the other terms are scalars that would mean adding a vector and a scalar.

And in the article on moddb it says

# Now, calculate the final lateral acceleration required for our missile
# to home into our target.
latax = RTM_new * N * Vc * LOS_Rate + LOS_Delta * Nt * ( 0.5 * N )

So the formula is supposed to result in a LATERAL acceleration - but the second part of the addition contains the factor Nt which is supposed to describe the relative acceleartion of the target - so if the target moves without acceleration it’s 0. (The article talks about flying missiles and therefore factors in gravity, but let’s talk about the special case of movement in a 2D plane first to check the formula - after all it should work just as well in that special case.

So this leaves us with

latax = RTM_new * N * Vc * LOS_Rate

and again RTM_new is a vector and all the other terms are scalars. So the result will have the same direction as RTM_new, and that vector is pointing directly at the target, so we end up accelerating at the target, not at a point in front of the target.

I’ll try to find the time and test it myself, but these two sources seem too faulty to simply copy and modify.

Warlaan | 2018-04-01 09:45

And how about the code above? I used another formula for 2D. I forgot to mention it. I tried to rotate using only LOS rate and navigation constant but it doesn’t work.

equus | 2018-04-02 18:13

I have written something like this:

https://github.com/JohnMeadow1/GodotGeometryElements/blob/master/final/targeting/scripts/rocket.gd

This will work in Godot 2.1.4

JohnMeadow1 | 2018-06-04 11:00

Thank you! I found you repository very interesting. The rocket script is very cool but I need something more similar to the link in my 3rd edit on this question. The code in the link worked but when I translated to Godot, something gone wrong and it not worked.
I ran your code downloaded from github. For some reason it didn’t work in my game. It worked only when I changed the orientation initial value to 90 degrees and set the object rotation to orientation - 90 degrees. I have no idea why.

equus | 2018-06-04 21:39

The problem might be related to where 0 degree is.
In math 0 degrees is RIGHT. But different math library implementations sometimes implement 0 degrees as UP. In my project, all sprites are oriented right so i do not need any ±90 degrees. Although I had this problem when changing to Godot 3.
I have uploaded Godot 3 version of the targeting project.
https://github.com/JohnMeadow1/GodotGeometryElements/tree/master/godot_3/final/targeting

If you can share your project I can look into it.

JohnMeadow1 | 2018-06-10 02:35

:bust_in_silhouette: Reply From: equus

Thank you all for the help! The problem was the initial rotation as JohnMeadow1 said.
I ended up using the following code:

func set_proportional_navigation_1(p_delta):
	var ninety_deg = (PI * 0.5) # = (PI / 2)
	
	_time_to_use_pn -= p_delta
	if _time_to_use_pn > 0:
		var cur_rot = parent_weapon.get_global_rot() + ninety_deg
		var forward_dir = Vector2(cos(cur_rot), -sin(cur_rot))
		current_velocity = forward_dir.normalized() * parent_weapon.w_speed
		return
		
	if enemy_target != null:
		
		var my_pos = parent_weapon.get_global_pos()
		var target_pos = enemy_target.get_global_pos()
		var cur_rot = parent_weapon.get_global_rot()
		
		var los = my_pos.angle_to_point(target_pos)
		var los_rate = los - _last_los
		_last_los = los
		
		var max_angle = PI * 0.083333 # = (PI / 12) = 15 degrees
		
		var angle = los_rate * navigation_const
		
		angle = clamp(angle, -max_angle, max_angle)
			
		parent_weapon.rotate(angle)
		
		cur_rot = parent_weapon.get_global_rot() + ninety_deg
		
		var forward_dir = Vector2(cos(cur_rot), -sin(cur_rot))
		current_velocity = forward_dir.normalized() * parent_weapon.w_speed