I’ve been at this a while now and I can’t pin down the solution. I’m working on foot ik/ground alignment. Video proof below shows that the math is mathing. The little blocks and axes represent the final foot bone orientation. This is the expected behavior. (Note that I modified the code for the video so that I could show this without the platform in the way. The IK itself doesn’t have any issues.)
The problem:
When I apply the final transform to the foot bones..
relative_transform.basis = Basis(target_rotation)
…they rapidly rotate around world y. I suspect it’s due to using the foot’s pose forward to build its ground relative forward, which then feeds back into itself on the next loop. I just can’t figure out how to anchor it. I’d prefer to not use a second raycast if I can help it.
My research points to SkeletonModifier3d being a way to get a reference to unmodified anim pose data, but I set up the appropriate signals and just couldn’t get anything to work.
The relevant code:
func _physics_process(delta: float) -> void:
update_foot_transform(ik_target_left, foot_left, rest_transform_left, raycast_left)
update_foot_transform(ik_target_right, foot_right, rest_transform_right, raycast_right)
## Move IK target to raycast hit position and rotate foot bone relative to hit normal
func update_foot_transform(
ik_target: Node3D,
foot_bone: int,
foot_rest_transform: Transform3D,
raycast: RayCast3D
) -> void:
var relative_transform: Transform3D = skeleton.get_bone_global_pose(foot_bone)
var world_position: Vector3 = skeleton.global_transform * relative_transform.origin
var up := Vector3.UP
# Lerp weight for how "grounded" a pose is
var ik_target_offset := Vector3.ZERO
var foot_rest_offset: float = abs(relative_transform.origin.y - foot_rest_transform.origin.y )
var influence: float = 1.0 - clamp(foot_rest_offset / ground_influence_max, 0.0 , 1.0)
# Neutral position for ik target and raycast - TODO: Smooth transition out of raycast hit
ik_target.global_position = world_position
raycast.global_position = world_position + ray_offset
# If colliding with ground - Update hit normal as UP and
# lerp the IK target between the anim pose position and ground
if raycast.is_colliding():
up = raycast.get_collision_normal()
ik_target_offset = up * ankle_offset
ik_target.global_position = lerp(
world_position, raycast.get_collision_point() + ik_target_offset,
influence
)
var forward: Vector3 = skeleton.global_basis * relative_transform.basis.y
forward = forward.slide(up).normalized()
var right: Vector3 = up.cross(forward)
# final basis/rotation for the tootsie
var target_basis := Basis(right, up, forward)
var target_rotation: Quaternion = target_basis.get_rotation_quaternion() * foot_rest_transform.basis.get_rotation_quaternion()
# There's some slerp related lines here that I have temp disabled
# Inject the final rotation into the original bone transform
relative_transform.basis = Basis(target_rotation)
# PROBLEM CODE BELOW
# Apply the new pose - This turns my feet into go go gadget propellers
#skeleton.set_bone_global_pose(foot_bone, relative_transform)
Yeah I think there’s just not a lot of information yet about working with the SkeletonModifier3D. I did read that page you linked, but I didn’t notice there was a demo project to download. I’ll give it a look.
Anyway, I was able to confirm my suspicion that the issue is the bone pose feeding back into itself. I cached a copy of the initial pose on ready and used that for the ground offset instead of the live pose. Obviously that won’t work for animation changes..
Also, there was a small error in my math. I was using global coords for the IK target node, but forgot to convert back to skeleton relative space for the bone orientation.
I really think the SkeletonModifier3D and its signals are the key. As I understand it, this would allow me to read pose states between > animation > IK > foot orientation. I just haven’t been able to work out how to divide the code up into the discreet steps. Maybe that demo project will provide some clues.
I done figured it out! I still have some transition polishing to do when the feet come off the ground, but foot-to-ground orientation is now working.
I think I’ve now got a decent understanding of the SkeletonModifier3D and how you coordinate the various states of the skeleton data through signals. So here’s what I’ve done for anyone else that come across this thread:
First I make a connection to the animation player > mixer_applied() signal,
and the right leg’s TwoBoneIK3D > modification_processed() signal.
EDIT: I just realized I can setup the IK for both legs within a single TwoBoneIK3D node.
Now I have:
func _on_mixer_applied() -> void:
# This is where I update my foot IK targets
# This runs after animations are applied to the skeleton and
# before any additional modifications are made to the skeleton
func _on_modification_processed() -> void:
# This is where I update the orientation of the feet to match the ground
# This runs after animation AND left/right TwoBoneIK3D is processed
Can this principle be used in the “opposite direction” — to modify the skeleton bones before applying animation — for example, to change the proportions of the rig (different arm and leg lengths)?
I’m not at my computer to confirm this, so naively speaking, I would imagine so. But you would probably have some work to do in compensating for how animations would affect longer/shorter limbs. It would certainly be a direction to investigate. I’d be curious to know what you find out.
This issue will arise with any rig other than the one on which the animation is being built. The plan is to solve it using IK.
There’s also the interaction between characters of different heights.
However, I’m afraid I don’t yet have the expertise to tackle a problem like this, so we’ll likely have to postpone investigating it for now. So it looks like a solution won’t be coming anytime soon.