In my Player node I want to rotate the node that has the model, so it faces the current direction at all times. I've been using look_at() but the transition is immediate and it's not smooth.

I have the following code for now, but I can't figure out how to smoothly apply the calculated angle to the current rotation of the node:

if direction != _last_direction:
	var target = position + direction

	var diff_angle = direction.angle_to(position)

	# Normalize the difference angle to range -PI to PI
	while diff_angle < -PI:
		diff_angle += 2 * PI
	while diff_angle > PI:
		diff_angle -= 2 * PI

	# Rotate ??

	_last_direction = direction

In this code:

  • direction: Vector3 with the current facing direction
  • _last_direction: Vector3 of the previous facing direction
  • target: the desired Vector3 to face, with this I calculate the angle that's stored in diff_angle

How can rotate a given node smoothly to face the target?

Tomcat Yes I could use a Tween, but first I need to calculate the rotation to apply, which is the key part that I'm missing.

    svprdga
    Use look_at(TARGET)
    To get a vector3 pointing towards the target
    Use that as the destination for a tween

      stranger_anger the look_at() method does not return a Vector3. In fact, I already have the Vector3 that points to the target. I have tried to use that target as the destination of the Tween and it doesn't work, the model is rotating in strange ways.

      • xyz replied to this.

        svprdga Gradually spherically interpolate between starting and wanted orientation quaternions using Quaternion::slerp(). You can animate the interpolation parameter either manually in _process() or using a tween. The latter is better if you also need to apply some easing.

        HI ...
        do this is _process or _physics_process ... if used in _process multiply amount by delta

        transform.basis = transform.basis.slerp( transform.looking_at( look_at_position ).basis, amount_of_rotation )

        where amount_of_rotation is between 0 and 1

        hehe I am still mess around with this, the motion of rotation of the objects I am trying to align with the target node's rotation is rather hilarious -.-

        1234.mp4
        2MB

        hmmm I messed with klaas's method a bit, and it seemed to work now:

        transform.basis = target_node.transform.basis.slerp( transform.looking_at( position ).basis, 0.01 )

        hmmm ... this seems odd ... you shouldn't use target_node.transform.basis.slerp

        The idea is ... transform.basis (rotation) is equal current rotation ( transform.basis ) slerped (interpolated) to rotation after a look_at ( transform.looking_at( position_of_node_to_look_at).basis )

        a month later
        8 months later

        I have figured out a solution:

        1) This is not perfect, see my (2) below.

        Without any speed_factor (interpolating by default amount):

        func _process(delta):

            var angle_to_target = int(    rad_to_deg(  get_angle_to(target_body)  )    )
        
            if(angle_to_target > 0): #rotate counter-clockwise if the target is on your left
        	global_rotation += 1 * delta
            else: #rotate clockwise if the target is on your right
        	global_rotation -= 1 * delta

        This is not perfect!
        Basically, if our angle to target is not 0 (we aren't facing it perfectly), we'll rotate ourselves towards it by unit amount every frame, thus gradually correcting our angle towards it. This means every frame, our angle_to_target will reduce because we're gradually turning towards it. However, if our angle_to_target is very close but still greater than zero, rotating by + (1 * delta) may overcorrect our angle resulting in angle_to_target becoming < 0 (we overdid our rotation towards it, and now we have to rotate back). In that case, the else block will execute and we'll rotate back by - (1 * delta) which may again overcorrect our angle to positive direction requiring us to rotate in the opposite direction again, and so on...
        This will result in a "jitter/vibration" as our body closes in on facing the target but can never reduce the angle perfectly to 0 (it'll either overcorrect it in the positive direction, or in the negative direction).
        To solve that, we should take a range of rotation values which is greater than the amount we are correcting it by, and if our angle reaches that range, instead of interpolating, we simply look_at(target_body) directly.

        2) In my updated code, not only are we addressing the jitter, we are also using an optional speed factor to rotate it faster or slower as we wish:
        func _process(delta):

        var speed_factor = 5
        var angle_to_target = int(    rad_to_deg(  get_angle_to(target_body)  )    )
        
        #lets take an angle range of 2 to -2 degrees (just make sure this range is bigger than the amount of rotation you're doing with adding/subtracting delta * speed_factor to our global_rotation, otherwise you'll get the jitter again)
        #So,
        if(angle_to_target <= 2 and angle_to_target >= -2): #if its close enough, we don't need to interpolate, just look at it directly, so no chance of overcorrection 
        	look_at(target_body)
        
        elif(angle_to_target > 2): #rotate counter-clockwise
        	global_rotation += 1 * delta * speed_factor
        
        else: #i.e., if angle_to_target < -2,  #rotate clockwise
        	global_rotation -= 1 * delta * speed_factor

        WARNING: Make sure your angle range (here 2 to -2 deg) is greater than delta * speed_factor to avoid jitter/overcorrection. If you're not using speed_factor, make sure your angle range is higher than delta to avoid jitter/overcorrection.
        Still, even this is not perfect, since the speed_factor isn't effective when we use look_at(), but its good enough for me.