How to solve rotation problems when looking up or down 90 degrees?

Godot Version

4.1.1

Question

When I rotate an object so that it looks straight up or down on the x axis the left right rotation on the y axis becomes a tilt rotation on the z axis. I am unsure if this is a gimbal lock or something else. Switching to use quaternions has also not fixed the issue either. Code below. ($Inner is a sub node of the parent node)

extends Node3D

func _process(delta):
	
	var mouse_speed = _get_mouse_speed()
	
	var current_qat = Quaternion(global_transform.basis)
	var a = (mouse_speed.x * 0.01)
	var y = sin( a / 2.0 )
	var w = cos( a / 2.0 )
	var new_ang = Quaternion(0, y, 0, w).normalized()
	current_qat = current_qat * new_ang
	global_transform.basis = Basis(current_qat)
	var current_qat2 = Quaternion($Inner.global_transform.basis)
	var a2 = (mouse_speed.y * 0.01)
	var x2  = sin( a2 / 2.0 )
	var w2 = cos( a2 / 2.0 )
	var new_ang2 = Quaternion(x2, 0, 0, w2).normalized()
	current_qat2 = current_qat2 * new_ang2
	$Inner.global_transform.basis = Basis(current_qat2)

	self.orthonormalize()
		
func _get_mouse_speed() -> Vector2:
	var screen_center = get_viewport().size * 0.5
	var displacement = screen_center - get_viewport().get_mouse_position()
	displacement.x /= screen_center.x
	displacement.y /= screen_center.y
	return displacement

I was able to fix the problem by changing $Inner.global_transform.basis to just global_transform.basis. However now I have some unintended rolling around the z axis.

#Removing $Inner from the above code fixes the problem but introduces unintended z roll. 
	var current_qat = Quaternion(global_transform.basis)
	var a = (mouse_speed.x * 0.01)
	var y = sin( a / 2.0 )
	var w = cos( a / 2.0 )
	var new_ang = Quaternion(0, y, 0, w).normalized()
	current_qat = current_qat * new_ang
	global_transform.basis = Basis(current_qat)
	var current_qat2 = Quaternion(global_transform.basis)
	var a2 = (mouse_speed.y * 0.01)
	var x2  = sin( a2 / 2.0 )
	var w2 = cos( a2 / 2.0 )
	var new_ang2 = Quaternion(x2, 0, 0, w2).normalized()
	current_qat2 = current_qat2 * new_ang2
	global_transform.basis = Basis(current_qat2)

Is there something wrong with Node3D.rotate(axis,angle)?

Yes. Same problem occurs using the below code for rotation when looking straight up or straight down 90 degrees. I also tried rotate_object_local and rotation_degrees.

self.rotate(Vector3(0,1,0),(mouse_speed.x * 70 * 0.0001))
$Inner.rotate(Vector3(1,0,0),(mouse_speed.y * 70 * 0.0001))

Rotate only the parent. The child will rotate around the parent automatically.

Sorry, I didn’t pick up on what you were trying to accomplish with the second rotation (I’m multitasking). Disregard what I said above.

I was able to get a more readable version of my above code that still suffers from the same problem, incorrect rotation when looking up/down 90 degrees which changes to incorrect z rolling when removing $Inner.

# Calculate rotation for self
var yaw = mouse_speed.x * 0.01
var yaw_quat = Quaternion(Vector3(0, 1, 0), yaw)

var current_qat = Quaternion(global_transform.basis)
current_qat = current_qat * yaw_quat
global_transform.basis = Basis(current_qat)

# Calculate rotation for $Inner object
var pitch = mouse_speed.y * 0.01
var pitch_quat = Quaternion(Vector3(1, 0, 0), pitch)

var current_qat2 = Quaternion($Inner.global_transform.basis)
current_qat2 =  current_qat2 * pitch_quat
$Inner.global_transform.basis = Basis(current_qat2)