Reputation: 471
I'm trying to rotate a bone in Godot by an angle and an axis I have already calculated. I've tested this angle and axis by rotating a Mesh Instance, and it works perfectly. I checked the calculations and they look right (the axis is found by taking the cross product of the two vectors making up the angle). However, when I try to rotate the bone by the same values, it rotates by the correct angle, but the axis is suddenly different (it should be rotating up, but it rotates to the left). Is this a problem with my code, or maybe is the 3d model/skeleton not made properly?
Here is my code:
# the vector is the Mesh Instance, and the rotation is correct
vector.rotate(axis, angle)
# the bone is rotated by the same values, but the axis is wrong
bonePose = skel.get_bone_pose(id)
bonePose = bonePose.rotated(axis, angle)
skel.set_bone_pose(id, bonePose)
Upvotes: 1
Views: 1694
Reputation: 471
There's another method that worked without the stretching that came from using .set_bone_global_pose_override()
. It turns out the axis was correct in global space where it was calculated, but wrong for the bone because it rotates locally relative to the skeleton. When the axis was transformed from global space to the bone's local space, the rotation was what I expected. I'm still not sure why the other method caused stretching.
Here's the code:
# transform from global space to local space
var bone_to_global = skel.global_transform * skel.get_bone_global_pose(bone)
var axis_local = bone_to_global.basis.transposed() * axis_global
# rotate the bone
bonePose = skel.get_bone_pose(bone)
bonePose = bonePose.rotated(axis_local.normalized(), angle)
skel.set_bone_pose(bone, bonePose)
Upvotes: 2
Reputation: 40170
You can write a global pose override:
bonePose = skel.get_bone_global_pose(id)
bonePose = bonePose.rotated(axis, angle)
skel.set_bone_global_pose_override(id, bonePose, 1.0)
The third parameter amount
is a weight. If it is 0, it leaves the pose as is, if it is 1.0 it replaces it. Values in between are interpolations. There is also a fourth optional parameter persistent
which specifies whether or not it should keep the pose override the next time skeleton is updated (it should compute the interpolation with the updated pose).
If that works for you, great.
The more fun to answer question for me, is how to do that without set_bone_global_pose_override
. Consider this an academic excercise.
The global pose of a bone is the global pose of the parent times the pose of the bone. Let us express that:
b.global_pose = parent.global_pose * b.pose
Actually, if there is a custom pose, then it is:
b.global_pose = parent.global_pose * b.custom_pose * b.pose
And if there is a rest pose, then it is:
b.global_pose = parent.global_pose * b.rest_pose * b.custom_pose * b.pose
And we want to modify the global pose, but we can only control the custom pose and the pose.
In fact, there is no reason to modify pose, since applying a transformation to the pose is equivalent to setting a custom pose.
Now, the question is what custom pose X
can we set so it is equivalent to applying a transform T
to the global pose:
T * b.global_pose = parent.global_pose * b.rest_pose * X * b.pose
Solve for X. Reminder: the transform multiplication is not commutative.
Start by applying the invert of the global pose of the parent by both side (they cancel on the right):
inverse(parent.global_pose) * T * b.global_pose = b.rest_pose * X * b.pose
And then the rest pose by both sides:
inverse(b.rest_pose) * inverse(parent.global_pose) * T * b.global_pose = X * b.pose
And then both sides by the inverse of the pose:
inverse(b.rest_pose) * inverse(parent.global_pose) * T * b.global_pose * inverse(b.pose) = X
And that is how we need to compute the new custom pose. The code for that would be something like this:
var inverse_rest = skel.get_bone_rest(id).affine_inverse()
var parent = ske.get_bone_parent(id)
var inverse_parent_global = Transform.IDENTITY
if parent != -1:
inverse_parent_global = skel.get_bone_global_pose(parent).affine_inverse()
var global_pose = skel.get_bone_global_pose(id)
var inverse_pose = skel.get_bone_pose(id).affine_inverse()
var t = Transform.IDENTITY.rotated(axis, angle) # the operation you want to do
var x = inverse_rest * inverse_parent_global * t * global_pose * inverse_pose
skel.set_bone_custom_pose(id, x)
Or if you want to set the pose instead of the custom pose (assuming the custom remains the identity trasnform):
var inverse_rest = skel.get_bone_rest(id).affine_inverse()
var parent = ske.get_bone_parent(id)
var inverse_parent_global = Transform.IDENTITY
if parent != -1:
inverse_parent_global = skel.get_bone_global_pose(parent).affine_inverse()
var global_pose = skel.get_bone_global_pose(id)
var t = Transform.IDENTITY.rotated(axis, angle) # the operation you want to do
var x = inverse_rest * inverse_parent_global * t * global_pose
skel.set_bone_pose(id, x)
Upvotes: 1