How to Convert Quaternions to rotation-only Transform3D for skeleton?

Godot Version

4.6

Tiny summary

How to convert Quaternion to Transform3D for skeleton bone pose?
skeleton.set_bone_pose(bone, Transform3D(Basis(some_quaternion))) gives wrong pose.
skeleton.set_bone_pose_rotation(bone, some_quaternion) works fine.
Math isn’t mathing. Want Transform, not Quaternion. What do?

Full Question (with images)

For some context: i am copypasting quaternions from animation keyframes into (bone → quaternion) pairs for posing skeletons. Because my code poses the skeleton with Transform3Ds, i convert these (bone index → quaternion) pairs into (bone index → Transform3D matrix) pairs.
I get wrong rotations when i convert Quaternion → Basis → Transform3D.
The animation parser loop looks like this:

var source_animation = player.get_animation(pose_name)
			for track_name in bone_track_indexes:
				# fetch the first key of each track considered a bone rotation track
				var the_rotation_quaternion: Quaternion = source_animation.track_get_key_value(bone_track_indexes[track_name], 0)
				var index_of_bone = host.skeleton.find_bone(track_name)
				all_the_poses[pose_name][index_of_bone] = Transform3D(Basis(Quaternion(the_rotation_quaternion)))
				#skeleton.set_bone_pose_rotation(index_of_bone, the_rotation_quaternion)
				#skeleton.set_bone_pose(index_of_bone, all_the_poses[pose_name][index_of_bone])
				print("animation `",pose_name, 
				"` contains keyframes for bone `", track_name,
				"` at track ", bone_track_indexes[track_name],
				", \n\twith first keyframe containing value `", the_rotation_quaternion,
				"` for bone ", index_of_bone)
				pass

if on that loop i uncomment:
skeleton.set_bone_pose_rotation(index_of_bone, the_rotation_quaternion),
the skeleton is forced into the correct pose by directly applying the quaternions:

However, if i uncomment:
skeleton.set_bone_pose(index_of_bone, all_the_poses[pose_name][index_of_bone])
instead, the skeleton gets a broken version of the original pose:

Even if i overlay the rest pose trying to fix it:
skeleton.set_bone_pose(index_of_bone, skeleton.get_bone_rest(index_of_bone) * all_the_poses[pose_name][index_of_bone]),
it’s still borked:

How am i supposed to generate a correct full loc-rot-scale matrix, for use with skeleton.set_bone_pose(), from a rotation-only quaternion? The math isn’t mathing for all the tricks i had up my sleeve.
I will likely ditch the matrices and use quaternions directly in the near future, but i’m pretty sure not understanding this transform conversion stuff will bite my backside when i do other 3D stuff later.

Since a quaternion only holds rotation info, I would suspect when you convert it to a Transform3D and use it with set_bone_pose, you’re overriding its position with a transform that has no position data. You’d need to get the bones position as well and apply it to all_the_poses.origin. Or just continue using set_bone_pose_rotation, which avoids overriding the position alltogether.

Pulling from your line of thought i did

var composite_mat = skeleton.get_bone_rest(index_of_bone)
composite_mat.basis = Basis(the_rotation_quaternion)
skeleton.set_bone_pose(index_of_bone, composite_mat)


Looks like i had the wrong expectations about animation quaternion keyframes.

  • Applying just the quaternion as a matrix was wrong because it was discarding the translation of the bone from the rest pose, thus correct rotation, but joints starting at wrong positions.
  • Applying the quaternion on top of rest transform was wrong because the quaternion already is the result of a delta stacked on top of rest rotation. In this case joints started at the right places, but rotation was wrong.

I’ll consider ditching the matrix conversion and applying rotation quaternions directly, but for now replacing basis of rest transform will do.