Joe
Joe

Reputation: 471

How to get Euler angles from two transforms in Godot

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?

Upvotes: 3

Views: 1691

Answers (1)

Theraot
Theraot

Reputation: 40170

Solution

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.


Explanation

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

Testing

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)

Upvotes: 2

Related Questions