How to set a maximum turn rate in 3D

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

Getting to the point: How can I set up an object so that it cannot rotate more than a given measurement in a frame on all axis in 3D?

Adding context: I’m trying to design a homing missle (albeit I think that this question is applicable to many sorts of moving objects). I can design a missle that can track its target perfectly simply by setting look_at every frame, but this results in something that is not very much fun because it cannot be dodged.

Edit: Also, to clarify, in the specific case, I’m working with a KinematicBody, albeit I reckon that this question could also apply to RigidBodies

How can I give it a maximum per-frame turning rate?

You did not specify whether your missile is a physical object. I planned to answer a Force applied answer so wait for whether current answer is accepted or not?

clemens.tolboom | 2021-02-09 12:36

The answer provided at the moment seems to leave out the part I was actually asking, so I’d certainly appreciate your input. Also, to answer your question, I’m using a kinematic body, but I imagine that this question also applies to rigidbodies

TheBroodian | 2021-02-09 18:14

:bust_in_silhouette: Reply From: DaddyMonster

You’ll need to code your own custom method for this, look_at does what it says on the tin and snaps the obj to the target on the negative z.

Several ways to do this. You could take the target’s vector and instead of setting the missile’s vector directly as look_at does you could lerp towards it. Linear interpolation is a bit on the advanced side though if you’re a beginner. So I’d recommend just comparing the direction to the target against your heading and rotating the obj accordingly checking each frame how aligned they are.

Getting the unit vector to the target is done with target_vectorminus missile_vectorand then normalising the result (bringing the values between zero and one, so just considering the direction, not the scale). This:

var target_vec = (target_obj.global_transform.origin-missile_obj.global_transform.origin).normalized()

Now you can calculate the dot product of your actual heading against your target heading:

var missile_heading = -missile_obj.global_transform.origin.z.normalized()
var dot_prod = missile_heading.dot(target_vec)

If you don’t know, dot product is a calculation where if two unit vectors are aligned then it outputs 1 and if they’re at right angles you get zero. Opposite way outputs -1. This way you can make the missile lose track in a realistic way if it’s outside a “viewing cone”. You’ll need to take orthogonal dot products to establish the bearing (the dot product says they’re not aligned, it doesn’t say which way) and then you can just rotate_object_local the missile according to these values with a maximum rotation set.

Finally, you just need to set a member variable with your max turn rate and put that in your rotate_object_localcall so that it never turns but more than however many radians you set.

Dot product is about the most useful thing in games so it’s well worth looking at a few tutorials.

Hope that helps.

Why is var missile_heading = -missile_obj.global_transform.origin.z.normalized() the missile heading? And what is this z … I’m a noob regarding global_transform still :wink:

clemens.tolboom | 2021-02-09 12:39

Ah, the wonderful world of linear algebra. :slight_smile:

Let’s do 2d to keep it simple: imagine you have something at Vector2(2, 0) and you add [move it by] Vector2(2, 1). You get Vector2(4, 1). Try to imagine this on graph paper with the missile moving.

Well, in this case you’re just doing this working backwards: your missile is at Vector2(2, 0), the target is at Vector(4, 1). What you want is the vector arrow pointing from the object to the target.

Just minus the target from the object. Vector2(4, 1)-Vector2(2, 0) = Vector2(2, 1). Exactly the same maths as before.

Now, you just want the heading so you normalise it: Vector2(2, 1).normalized(). Dot product won’t work as expected if things aren’t normalised. So, now we know the target is in the positive x direction and a bit on the y direction. (print this to console to see what the actual result is)

Btw, there’s a video course on maths for game devs on humble bundle for 1 EUR atm which covers this. I recommend it.

DaddyMonster | 2021-02-09 13:21

:bust_in_silhouette: Reply From: clemens.tolboom

Using a VehicleBody I came with Rocket.gd

extends VehicleBody

var speed = 100.0

onready var target = get_node('../Target')
onready var front_of_rocket = $CSGSphere

func _physics_process(delta):
	var front:Vector3 = front_of_rocket.global_transform.origin
	var me:Vector3 = global_transform.origin
	var him:Vector3 = target.global_transform.origin

	var dir:Vector3 = (him - me).normalized()
	var rot_towards:Vector3 = (him - front).normalized()
	var front_dir:Vector3 = (front - me).normalized()

	# We want to rotate towards the target
	var torque = -rot_towards.cross(front_dir)

	# Make sure angular_velocity has damping ~ 1.0
	add_torque(80 * torque.normalized())

	# Engine is always pushing the rocket forward
	# Make sure lineair_velocity has some damping
	add_force(front_dir * speed, front_dir)

The Scene tree looks like

Spatial
    Rocket: VehicleBody
        CollisionShape
        Cylinder
        CSGSphere
    Target

The torque force is normalised and the used multiplier/limiter is dependent on angular_velocity damping. Same goes for linear_velocity damping. Experimental I used the values below.

func _ready():
	angular_damp = 1.0
	linear_damp = 0.2

Changing the mass from 40kg to 10 kg helps too. I’m still puzzled about how to fly the rocket when Gravity Scale <> 0.0?

clemens.tolboom | 2021-02-10 12:20