How to properly create a herarchy of 3d bones with gdscript

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By kanator

I have been hammering at this for a few weeks now and have not been able to figure it out.

I am creating an importer for Hash Animation Master models.
I am loading all the bones and I am assigning the parents of each bone. But when I do that they start appearing in the wrong places with the wrong rotation.

If I just create the bones and do not assign the parent bone they appear in the correct location and rotation.

The problem I am having is that I have not been able to figure out how to properly convert their global position and rotation to the position and rotation when under a parent bone.

How to convert the location to local rotation?

I just was able to place the bones in the right position(local to parent position) when assigning their parents, but still cannot figure out how to properly convert the rotation to local.

This is what I did to place the bones in the correct position after parenting them.
Note in the code “bone” is an object of a class type I created that holds the global position, rotation, and parent bone. This is what is used to create the bones in the skeleton

1-First, create all the bones in the skeleton as global bones. Make sure the bones are being created from the main parent to the deepest child. This way we ensure that every parent bone index in the skeleton is smaller than their child’s bone index(This is required by Godot).

func _add_bone_globaly(bone)->void:
		var t:Transform = Transform(Basis(bone.rotation), bone.start)
		t = t.scaled(Vector3(1,1,1))
		_skeleton.add_bone(bone.name)
		bone.bone_id_in_skeleton = _skeleton.find_bone(bone.name)
		_skeleton.set_bone_rest(bone.bone_id_in_skeleton, t)
		bone.acumulated_basis = _skeleton.get_bone_global_pose(bone.bone_id_in_skeleton).basis
		
        _add_bone_tip(bone)#for debugging purposes
		if bone.children.size() > 0:
			for i in range(bone.children.size()):
				_add_bone_globaly(bone.children[i])
		pass

func _add_bone_tip(bone)->void:#For debugging purposes in order to be able to see the full bone
		var bone_global:Transform = Transform(bone.rotation, bone.start)
		_skeleton.add_bone(bone.name + "-" + "Tip")
		var tip = _skeleton.find_bone(bone.name + "-" + "Tip")
		var tip_local_translation:Vector3 = bone_global.xform_inv(bone.end)
		_skeleton.set_bone_rest(tip, Transform(Vector3(0,0,0),tip_local_translation))
		_skeleton.set_bone_parent(tip, bone.bone_id_in_skeleton)
		pass

2-Go through all the bones and assign their parent in the skeleton and converting the their global position/origin into a local position/origin to the parent bone.

func _assign_bone_parent(bone)->void:
	if bone.parent == null: return
	
	var parent_local:Transform = _skeleton.get_bone_global_pose(bone.parent.bone_id_in_skeleton)
	var child_global:Transform = _skeleton.get_bone_rest(bone.bone_id_in_skeleton)
	
	var t:Transform = Transform(Vector3(), parent_local.xform_inv(child_global.origin))
	t = t.scaled(Vector3(1,1,1))
	_skeleton.set_bone_parent(bone.bone_id_in_skeleton, bone.parent.bone_id_in_skeleton)
	_skeleton.set_bone_rest(bone.bone_id_in_skeleton, t)
	pass

How to convert the location to local rotation?

kanator | 2020-01-25 14:13

:bust_in_silhouette: Reply From: kanator

At last, this is the full solution for adding a child 3d bone. It will convert global translation and rotation to the parent bone local.

func _assign_bone_parent(bone)->void:
		if bone.parent == null: return
		
		var parent_global:Transform = _skeleton.get_bone_global_pose(bone.parent.bone_id_in_skeleton)
		var child_global:Transform = _skeleton.get_bone_rest(bone.bone_id_in_skeleton)
		var rot:Quat = parent_global.basis.get_rotation_quat().inverse() * child_global.basis.get_rotation_quat()
		var t:Transform = Transform(rot, parent_global.xform_inv(child_global.origin))
		_skeleton.set_bone_parent(bone.bone_id_in_skeleton, bone.parent.bone_id_in_skeleton)
		_skeleton.set_bone_rest(bone.bone_id_in_skeleton, t)
		pass