How do I smoothly rotate an object using rotate_x, rotate_y and rotate_z in combination?

I want to smoothly rotate a cube in x, y and z directions in 90 degree steps. For this I want to use rotate_x, rotate_y and rotate_z with target_rotation and then rotate the object in _process until the target rotation is reached. How can I adjust the target_rotation correctly, if a target has already been set and the object is partially rotated (problems of Euler angles) and how do I check whether the target_rotation has been reached in order to reset the target?

		if target_rotation.x:
			rotate_x(stepRotate);
		if target_rotation.y:
			rotate_y(stepRotate);
		if target_rotation.z:
			rotate_z(stepRotate);
		if ?????:
			target_rotation = Vector3.ZERO;

		if input == "rotate_left":
			target_rotation.z += +PI/2;
		elif input == "rotate_right":
			target_rotation.z += -PI/2;
		elif input == "rotate_top":
			target_rotation.x += -PI/2;
		elif input == "rotate_bottom":
			target_rotation.x += +PI/2;
		elif input == "rotate_forward":
			target_rotation.y += +PI/2;
		elif input == "rotate_back":
			target_rotation.y += -PI/2;

You could do it like this:

func _process(delta):
    if input == "rotate_left":
		target_rotation.z = wrapf(target_rotation.z + PI/2, 0, TAU);
	elif input == "rotate_right":
		target_rotation.z = wrapf(target_rotation.z - PI/2, 0, TAU);
	elif input == "rotate_top":
		target_rotation.x = wrapf(target_rotation.x - PI/2, 0, TAU);
	elif input == "rotate_bottom":
		target_rotation.x = wrapf(target_rotation.x + PI/2, 0, TAU);
	elif input == "rotate_forward":
		target_rotation.y = wrapf(target_rotation.y + PI/2, 0, TAU);
	elif input == "rotate_back":
		target_rotation.y = wrapf(target_rotation.y - PI/2, 0, TAU);

    var toTarget = target_rotation - rotation
    
    if toTarget != Vector3.ZERO:
        rotation = rotation.move_toward(target_rotation, TAU * delta)
        

If you want something that is not linearly interpolated but smoother, you should look into smoothing functions.
Functions used:

I’m not exactly sure on your use-case but do you need to “reset the target”?

Thanks for the reply but this will not fix the euler angle problem, e.g. is the result different if you first rotate x and then z or rotate z and then x. This is why I use rotate_x instead of rotation.x.
If I press a button, I want to rotate it smoothly 90 degree, so I need to “reset the target rotation”. For this I need an if-statement checking the object-rotation reached the target_rotation to set target_rotation = Vector3.ZERO.
I also want to change the target_rotation while the object is already partial rotated in an other direction. The problem is the axis changing while rotating on the other axis, so I need an conversion :smiley:

I must admit that you are being vague/confusing as #$@!.
Maybe you can elaborate on the following points?:

  • What is the “euler angle problem” that you are talking about?
    • Unless you want to find a way to not use euler angles (e.g use quaternions instead), I don’t see what this “euler angle problem” is.
  • What do you mean by smoothly rotating in 90 degree steps?
  • What is your actual problem?
    • I don’t understand the original problem statement, so maybe you could try and precisely explain your problem. You also seem to have a pretty good idea of how to solve your own issue:

What is missing in your own solution?

Sorry for the confusion but It’s not that easy to explain as a beginner :slight_smile:

  • Using 3D transforms — Godot Engine (4.2) documentation in English
  • Imagine a 3D Tetris and you want to rotate an “L” object around the three axes without changing the keyboard layout.
  • I’ve a Node3D with an script and three MeshInstance3D childs, which form a centered “L”. In the script I want to press D key once to rotate smoothly the Node3D around the global Y axis for 90 degree. If I press Q key once the Node3D should smoothly rotate the global Z axis. This should also work in combination, e.g. if I press the D key, wait until the Y axis is at approx. 45 degrees and then press the Q key. The result should be the same.

Thank you for wanting to help me!

Okay, so let me see if I get this right.

  1. You want to smoothly rotate an object around the global axes (X, Y, and Z)
  2. The rotation for each axes should be moved by 90 degrees when the following input is pressed
    • W/S: Rotate on X-axis
    • A/D: Rotate on Y-axis
    • Q/E: Rotate on Z-axis
  3. You want the final result of the rotation operation to be the same regardless of input timing.

If the above is correct, how does the previously provided code not work for you?
Do you have the object nested in another object? Because in that case you should use global_rotation instead of rotation.

You need to unlearn and forget about Euler angles, as they are more trouble than they are worth. Learn how to do axis+angle rotations, as they are simpler and much more useful.

You don’t have to learn the math, as Godot does that for you. You just have to learn what the cross product is and when to use it (Godot has a built-in cross product function). Smooth, accurate rotation then becomes rather simple.

I would then write a simple class that does a rotate(axis,angle) in small increments until it reaches the target angle, then sets the final rotation from the starting angle to the ending angle to eliminate floating point drift.

It sounds a lot harder than it actually is.

Here an example that illustrates the problem (based on Sweatix suggestion). It works almost correctly but depending on the rotated position, the axis of rotation changes. Just try it, it shows the problem :smiley:

var target_rotation = Vector3.ZERO;

func _process(delta):
	if Input.is_action_just_pressed("rotate_left"):
		target_rotation.z += +wrapf(PI/2, 0, TAU);
	if Input.is_action_just_pressed("rotate_right"):
		target_rotation.z += -wrapf(PI/2, 0, TAU);
	if Input.is_action_just_pressed("rotate_top"):
		target_rotation.x +=-+wrapf(PI/2, 0, TAU);
	if Input.is_action_just_pressed("rotate_bottom"):
		target_rotation.x += +wrapf(PI/2, 0, TAU);
	if Input.is_action_just_pressed("rotate_forward"):
		target_rotation.y += -wrapf(PI/2, 0, TAU);
	if Input.is_action_just_pressed("rotate_back"):
		target_rotation.y += +wrapf(PI/2, 0, TAU);

	var toTarget = target_rotation - rotation;
	if toTarget != Vector3.ZERO:
		rotation = rotation.move_toward(target_rotation, TAU * delta);

Here is a working-around for the problem, but the rotation looks sometimes strange.

Is there a better solution?

@onready var target = Node3D.new();

func _process(delta):
	if Input.is_action_just_pressed("rotate_left"):
		target.rotate_z(+PI/2);
	if Input.is_action_just_pressed("rotate_right"):
		target.rotate_z(-PI/2);
	if Input.is_action_just_pressed("rotate_top"):
		target.rotate_x(-PI/2);
	if Input.is_action_just_pressed("rotate_bottom"):
		target.rotate_x(+PI/2);
	if Input.is_action_just_pressed("rotate_forward"):
		target.rotate_y(-PI/2);
	if Input.is_action_just_pressed("rotate_back"):
		target.rotate_y(+PI/2);

	var toTarget = target.rotation - global_rotation;
	if toTarget != Vector3.ZERO:
		rotation = rotation.move_toward(target.rotation, TAU * delta);
	else:
		target.rotation = rotation;

If you don´t need to detect collisions, then maybe what you need is a tween.

Here is the solution:

@onready var target = Node3D.new();

func _process(delta):
	if Input.is_action_just_pressed("rotate_left"):
		target.rotate_z(+PI/2);
	if Input.is_action_just_pressed("rotate_right"):
		target.rotate_z(-PI/2);
	if Input.is_action_just_pressed("rotate_top"):
		target.rotate_x(-PI/2);
	if Input.is_action_just_pressed("rotate_bottom"):
		target.rotate_x(+PI/2);
	if Input.is_action_just_pressed("rotate_forward"):
		target.rotate_y(-PI/2);
	if Input.is_action_just_pressed("rotate_back"):
		target.rotate_y(+PI/2);

	var toTarget = target.rotation - rotation;
	if toTarget != Vector3.ZERO:
		var q  = Quaternion(transform.basis);
		var tq = Quaternion(target.basis);
		# Interpolate using spherical-linear interpolation (SLERP).
		transform.basis = Basis(q.slerp(tq, 0.05));		# find halfway point between q and tq and apply back
	else:
		target.rotation = rotation;