I have a Transform
that is rotated by an angle on an axis. Is it possible to get the Euler angles for the rotation using the Transform
before rotation and the Transform
after rotation? I tried using Transform.basis.get_euler()
, to get the Euler angles of the Transform
before and after. However I don't think subtracting them would work. How would this be done?
I came up with this:
var A := start.basis.get_rotation_quat()
var B := end.basis.get_rotation_quat()
var R := A.inverse() * B
print(R.get_euler())
It is also recommended to always normalize
quaternions to minimize cumulative floating point errors. But I believe it is not a problem here.
The idea is that you have a rotation quaternion A
that after some rotation R
becomes B
. In other words:
B = A * R
And if we solve for R
, we get this:
B = A * R
B * R.inverse() = A * R * R.inverse()
B * R.inverse() = A
B.inverse() * B * R.inverse() = B.inverse() * A
R.inverse() = B.inverse() * A
R = (B.inverse() * A).inverse()
R = A.inverse() * B
This is what I came up for testing it:
tool
extends MeshInstance
func _enter_tree() -> void:
set_notify_transform(true)
func _notification(what: int) -> void:
if what == NOTIFICATION_TRANSFORM_CHANGED:
var parent := get_parent() as Spatial
var end := global_transform.basis.get_rotation_quat()
var start := parent.global_transform.basis.get_rotation_quat()
var A := start
var B := end
var R := A.inverse() * B
var S := transform.basis.get_rotation_quat()
if R.w < 0: R = -R
if S.w < 0: S = -S
if not S.is_equal_approx(R):
prints("FAIL", rotation_degrees, R, S)
Attach it to a MeshInstance
child of an Spatial
.
The code will run on the editor. It enables a notification for any change in its Transform
. When it get the notification, it computes the quaternion from its global_transform
and its parent global_transform
. Rotate it around and see it if prints.
By the way, I had initially forgot that there are two ways to represent the same rotation with quaternions. So it wasn't passing the test. These lines fix it:
if R.w < 0: R = -R
if S.w < 0: S = -S
No, normalize
does not fix this. And the lack of normalize
didn't cause the test to fail either.
I also tested that you don't need to do that to get the correct value from get_euler
. I mean, like this it works correctly:
var end := global_transform.basis.get_rotation_quat()
var start := parent.global_transform.basis.get_rotation_quat()
var A := start
var B := end
var R := A.inverse() * B
var S := transform.basis.get_rotation_quat()
if not S.get_euler().is_equal_approx(R.get_euler()):
prints("FAIL", rotation_degrees, R, S)
Similarly it works correctly with Transform
directly, like this:
var end := global_transform
var start := parent.global_transform
var A := start
var B := end
var R := A.affine_inverse() * B
var S := transform
if not S.is_equal_approx(R):
prints("FAIL", rotation_degrees, R, S)