Quaternion Slerp Constant

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

Hello, i’ve been working alot on a tank game prototype, and it is going very well, but there is still a problem i’m not being able to fix, this problem is the tank turret rotation, i decided to use i diferrent approach to getting it done, so now i have a Spatial node that points in the direction of the hit location of a raycast that comes from the camera, and from there i rotate the turret of the tank in the Y axis and the gun of the tank in the X axis to match the rotation of this Spatial node (i know it is a bit hacky, probably, but it gives the best result of everything i tried).

here’s the code:

var desired_rotation = turretG.global_transform.looking_at(cam.ray_pos, Vector3.UP)
var a = Quat(turretG.global_transform.basis.get_rotation_quat()).slerp(desired_rotation.basis.get_rotation_quat(), delta * turn_quant * turn_turret)
turretG.global_transform = Transform(a, turretG.global_transform.origin)
	
turret.rotation.y = turretG.rotation.y
maingun.rotation.x = turretG.rotation.x
maingun.rotation.x = clamp(maingun.rotation.x, deg2rad(depression), deg2rad(elevation))

my problem with it is that i can’t keep its rotation consistent, it starts rotating fast and then it slows down when it is getting closer to its desired rotation, and gameplay wise that would not be fun to play with.

so is there a way i could make this rotation more consistent? any help would be appreciated.

:bust_in_silhouette: Reply From: Pyroka

Edited Answer
(Answer checked with Godot 4.0.2) I answer in case some people google and find it.

If you want constant movement in your slerp / lerp you have to slerp with a constantly increasing value until 1 is reached.

If you only multiply a lerp/slerp by a factor and delta, it slows down because the factor to slerp is always constant (+ - framrate) but the distance to slerp decreases. Therefore it it never finishes the slerp it just takes the same percentage from an decreasing distance which looks like a it is slowing down.

I have working code that rotates to the target with a constant speed given in rad per secs (e.g. 90° per second is a speed of PI/2). The idea is to calculate the slerp percentage for 1° rotation. Then we can multiply it with our speed and the delta time to get the desired result (90° per second).

Code:

## Considers -Z as forward axis!
func rotate_to_slerp(target: Node3D, delta: float) -> void:
	# rotation basis (transform) to target
	# looking_at is the transform it would be when looking at target
	# target.position is same as target.global_transform.origin
	var rotation_basis = global_transform.looking_at(target.position, Vector3.UP).basis
	# Lerp factor calculation (° in rad) example for 90° (PI/2) per sec:
	# 1.0 (100%) = target_angle | factor for target angle
	# 1.0/target_angle = 1° | factor for 1°
	# 1.0/target_angle * 90°= 90° | factor for 90°
	# 1.0/target_angle * 90° * delta = 90°per sec |(sum_delta = 1 after 1 sec)
	var angle = rotation_basis.get_rotation_quaternion().angle_to(transform.basis.get_rotation_quaternion())
	var lerp_amount = (1/angle) * delta * (PI/2)  # (PI/2) would be a speed var
    # tolerance check to not rotate for tiny amounts
	if rad_to_deg(abs(angle)) > 1:
		global_transform.basis = global_transform.basis.slerp(rotation_basis, lerp_amount)

Old Answer for simple cases
For example the sum of the delta time capped to a maximum of 1. It then takes exactly 1 second for the slerp to finish. Dividing this by a factor slows it down.

Example to rotate to face a target within 2 seconds:

var deltasum = 0   # needs to be reset for a new target
func _physics_process(delta):
	
	if Input.is_action_pressed("debug_btn"):
		var rotation_basis = global_transform.looking_at(ingametarget.position, Vector3.UP).basis
		if deltasum < 1:
			deltasum += delta
		global_transform.basis = global_transform.basis.slerp(rotation_basis, deltasum/2)

Problem with this solution is that it has a constant time to finish the rotation but not a constant speed to do so. Meaning 180° will take 2 seconds and so will 360° rotation.