Search Unity

How to Aim Animator.SetIKRotation() to direction WRT hand bone rotation offset?

Discussion in 'Animation' started by LaneFox, Sep 18, 2015.

  1. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,519
    I'm trying to make a firearm which is parented to the hand aim directly forward to where the character is facing.

    I assumed this would be easy with

    Code (csharp):
    1. Animator.SetIKRotation(AvatarIKGoal.RightHand,Quaternion.LookRotation(SubjectObj.transform.forward));
    But I'm testing this on 4 different rigs and getting inconsistent results because of the default orientation of hand bones - also screwing it up is the axis of the hand bones.

    Currently the weapon is put into the hand like this:
    Code (csharp):
    1. Quaternion WeaponOrientationInHand()
    2. {
    3.    Quaternion foo =Quaternion.LookRotation(
    4.      (Subject.InvertHandForward ? -RightHandBone().right : RightHandBone().right),
    5.      RightHandBone().TransformDirection(Subject.Stats.ThumbDirection));
    6.    return foo;
    7. }
    bonetransform.right is X aiming down the arm. Some rigs have it inverted.. This part actually works great. The weapon sets in the hand perfectly every time even with totally jacked up bone setups.

    Then the weapon it parented to the hand bone and I make IK calls to put the hand bone where I want it, but then I need to rotate the hand so that the gun actually points forward but I can't seem to figure out the rotation math to do it. I'm stuck here.

    I've tried various things to get the rotation but bottom line I can't figure out how to compensate for the base hand rotation offset variance even when I know all of the correct variables. ThumbDirection and X direction should be enough to formulate a signed direction but I need some help figuring out doing this with the rotations respecting that.

    Tried posting this on Answers, but for some reason my posts now go to Moderation?
     
    Last edited: Sep 18, 2015
    Cichol likes this.
  2. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,519
    Here are some pics to illustrate what is happening.

    I'm kind of at a loss why this doesnt work:
    Code (csharp):
    1. Animator.SetIKRotation(AvatarIKGoal.RightHand,Quaternion.LookRotation(SubjectObj.transform.forward));
    The LookRotation() should align the Hand forward axis to the player's forward axis, right?

    shot_150918_190655.png shot_150918_190717.png shot_150918_190844.png

    The Weapons set into the hands correct, the IK just doesn't seem to set the rotation precisely. Tried some options with layering in the Animator too, didn't seem to change anything.
     
  3. Simo

    Simo

    Joined:
    Sep 16, 2012
    Posts:
    85
    Here is a helpful class to test you hand ik target

    1. create a new scene
    2. add you character with script below attached
    3. create a sphere/cude and drop it to rightHandIKGoal variable

    instead of using this Quaternion.LookRotation(SubjectObj.transform.forward) you can just change the position/rotation of the rightHandIKGoal, I think it's much easier and intuitive

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [ExecuteInEditMode]
    5. public class HandIK : MonoBehaviour {
    6.  
    7.     public bool enabledIK;
    8.     public Transform rightHandIKGoal = null;
    9.    
    10.     Animator animator;
    11.  
    12.     // Use this for initialization
    13.     void Start () {
    14.  
    15.         animator = GetComponent<Animator>();
    16.     }
    17.    
    18.     // Update is called once per frame
    19.     void Update () {
    20.  
    21.         animator.Update(0.0f);
    22.    
    23.     }
    24.  
    25.     void OnAnimatorIK(int layerIndex)
    26.     {
    27.         if (enabledIK)
    28.         {
    29.             animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1.0f);
    30.             animator.SetIKRotation(AvatarIKGoal.RightHand, rightHandIKGoal.rotation);
    31.  
    32.             animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1.0f);
    33.             animator.SetIKPosition(AvatarIKGoal.RightHand, rightHandIKGoal.position);
    34.         }
    35.         else
    36.         {
    37.             animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 0.0f);
    38.             animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 0.0f);
    39.         }
    40.     }
    41. }
    42.  
     
  4. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,519
    Yes but the problem is that the hand does not align to the given target. There is some sort of offset from the rig that the IK does not take into consideration.

    Thats why I'm building a rotation to add to it to make up the difference, but my efforts to do that haven't been working.

    Here's the current code, which basically produces the same result as the LookRotation() alone.
    Code (csharp):
    1.  
    2. private void SetRightHandIk()
    3.         {
    4.             Animator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1);
    5.             Animator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1);
    6.  
    7.             Animator.SetIKPosition(AvatarIKGoal.RightHand, DominantWeaponRestPoint() * CharScaleMultiplier);
    8.             Animator.SetIKRotation(AvatarIKGoal.RightHand, desiredRotation);
    9.  
    10.             _deltaRotation = Quaternion.Inverse(RightHandBone().rotation) * SubjectObj.transform.rotation;
    11.             desiredRotation = RightHandBone().transform.rotation*_deltaRotation;
    12.         }
    Here are the results.....
    shot_150919_140845.png

    Red line are actual, green lines are what it should be.

    The zombie is the worst rig, the other 3 are pretty good but all yield different offsets. In any case, the detal calc and multiplication isn't working to fix the issue.
     
  5. Simo

    Simo

    Joined:
    Sep 16, 2012
    Posts:
    85
    I don't really understand what you do here

    1. _deltaRotation = Quaternion.Inverse(RightHandBone().rotation) * SubjectObj.transform.rotation;
    2. desiredRotation = RightHandBone().transform.rotation*_deltaRotation;

    RightHandBone() is a Transform ?
     
  6. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,519
    Yes, its just a shorthand method. The code should be getting the delta rotation between where it should be pointing and where it is pointing, then adding it to the desired rotation to compensate for the offset. It doesnt seem to work.

    Code (csharp):
    1. Transform RightHandBone() { return Animator.GetBoneTransform(HumanBodyBones.RightHand); }
     
  7. Simo

    Simo

    Joined:
    Sep 16, 2012
    Posts:
    85
    so finaly desiredRotation is SubjectObj.transform.rotation

    you don't need to make inverse and multiply it by it self its an identity, right ?
     
  8. Simo

    Simo

    Joined:
    Sep 16, 2012
    Posts:
    85
    I think if you use the script I sent earlier, you can control the aim easly.

    and to avoid an extra computation, you can create the rightHandIKGoal with a child Transform, and apply all the offset to the child

    animator.SetIKRotation(AvatarIKGoal.RightHand, rightHandIKGoal.child(0).rotation);

    in that way you can control rightHandIKGoal in the same way for all your character
     
  9. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,519
    I need to pragmatically fix the rotation, i can't manually do it in the inspector for each character because it will never be exact. The entire problem is that SetIKRotation is not setting the rotation correctly, there is some inherit offset from each model's hand rotation that is screwing it up and must be fixed pragmatically.
     
    LazloBonin likes this.
  10. Simo

    Simo

    Joined:
    Sep 16, 2012
    Posts:
    85
    I can't really help cause I don't have such model's, if you know any free model that I can do a test with send me a link
     
  11. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,519
  12. Simo

    Simo

    Joined:
    Sep 16, 2012
    Posts:
    85
    will give it try and let you know
     
  13. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,519
    Thanks, here is what I'm doing for the debug rays.
    Code (csharp):
    1.  
    2.         void Update()
    3.         {
    4.             Debug.DrawRay(RightHandBone().position, RightHandBone().right * 2, Color.red);
    5.             Debug.DrawRay(RightHandBone().position, SubjectObj.transform.forward, Color.green);
    6. // the zombie's X is actually inverted, so you need -RightHandBone().right
    7.         }
    Technically this problem should be evident on any rig, I've used 4 different ones with the same Animator Controller and they all exhibit some amount of offset caused by how the model is rigged/setup which Mecanim's retargeting does not compensate for.

    The goal is to get the hand to orient inline with a given direction.
     
  14. Simo

    Simo

    Joined:
    Sep 16, 2012
    Posts:
    85
    Code (CSharp):
    1. Transform arm = animator.GetBoneTransform(HumanBodyBones.RightUpperArm);
    2. Quaternion lookAt = Quaternion.LookRotation(rightHandIKGoal.position - arm.position);
    3.  
    4. animator.SetIKRotation(AvatarIKGoal.RightHand, rightHandIKGoal.rotation * lookAt );
     
    Last edited: Sep 20, 2015
  15. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,519
    That doesnt seem to work.
     
  16. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,519
    Ok either I'm going crazy, or there is some magical hand rotation offset buried in the Humanoid rig that you have to have to use to get the hand to point at something properly.

    Code (csharp):
    1. if (DummyTarget) Animator.SetIKRotation(AvatarIKGoal.RightHand, Quaternion.LookRotation(DummyTarget.position - RightHandBone().position));
    Produces this:
    shot_150920_133449.png shot_150920_134052.png

    I'm at wits end. It seems like a bug at this point. The wireframe of the IK target isn't even in the right spot!

    The only way that even sort of works is adding a 'correction' vector to the LookRotation(), then tweaking it until it looks straight and applying that to the prefab. It's not perfect, but it seems like the best that can be done at this point.
     
  17. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,519
    FYI for those interested I filed a bug report on this last night and it was repro'd/accepted this morning. (<12 hrs[!])

    You can also get the delta quaternion and iterate closer to the correct direction, I was just doing the math wrong. It still doesn't seem to get it perfect, but I think that has something to do with the same issue thats causing the offset to begin with. The same exact code in Debug rays does what it is intended to do.
     
    theANMATOR2b likes this.
  18. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    @LaneFox So we did investigate this issue and the offset that you see is related to your avatar T-pose. If you want a perfect match you need to align your hand characterization pose on the X axis.
     
    theANMATOR2b likes this.
  19. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,519
    @Mecanim.Dev Ahh, I found where you can adjust the bone rotation in the Avatar Configuration screen. Was not aware we could make changes there. Did not test to confirm yet - out of town all this week.

    Is there a way to auto-align this or bind the avatar into a known T-pose that will alleviate alignment issues like this one? It's not ideal to have users manually adjust their avatars by eyeballing the hand alignment, especially so if you need to rely on the hand pointing in a specific direction with consistency.
     
  20. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    The best way would be to keyframe your t-pose in your authoring tools (3dsmax, maya, blender, etc...) and then import the file to create your avatar.
     
    theANMATOR2b likes this.
  21. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,519
    Certainly, but thats not always an option unfortunately as a majority of users are getting their characters from the asset store and just about every one will exhibit this issue. Currently it seems like the best way around this is to set the rotation in LateUpdate, bypassing SetIkRotation and the need for Avatar adjustments.
     
  22. Martin-Kraus

    Martin-Kraus

    Joined:
    Feb 18, 2011
    Posts:
    617
    I don't think this is a valid answer: the documentation of SetIKRotation states (http://docs.unity3d.com/ScriptReference/Animator.SetIKRotation.html ): "This function sets the rotation of the ultimate goal in world space".
    And world space is world space and not some rotated space that depends on the hand characterization pose. Thus, at least this is a mistake in the documentation.
     
    Claytonious, LazloBonin and LaneFox like this.
  23. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,519
    The demo provided reproduced the issue clearly but the bug report was closed as "By Design" which basically means the method literally gives an incorrect result on purpose. It should work like described, there isn't any reason for it to not do what it is currently documented to do. What else would it do?

    Claiming it is not a bug is baffling in the first place and manually tweaking the avatar is not precise enough (or good enough) to be a valid workaround so the only option is to not use SetIKRotation if you need a precise result which forces you to roll your own solution and/or use LateUpdate after the Animation pass.
     
    LazloBonin and Martin-Kraus like this.
  24. Martin-Kraus

    Martin-Kraus

    Joined:
    Feb 18, 2011
    Posts:
    617
    I'm working on a workaround based on the observation that the difference between the specified goal orientation and the actual goal orientation in world space appears to be a constant rotation. By specifying a goal quaternion "specified" with weight 1 and observing the resulting quaternion "resulting", one can compute this constant "offset" quaternion as

    offset = Quaternion.Inverse(resulting) * specified;

    Once "offset" is known, one can specify "specified * offset" as goal orientation, which should result in an orientation that is actually equal to "specified". So far, it appears to work well but I need to do more experiments.
     
    theANMATOR2b and LaneFox like this.
  25. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,519
    I went down that road but every rig has a different offset (it was always consistent, though). I did make it work by manually adjusting a rotation until it aligned with the correct direction, then storing that as the 'offset' for that particular model but I did not like the workflow. In the end the workflow I did choose is still similar, but only needs the palm and thumb local direction axis with optional right angle additions.

    If you can define the correct offset through code, I'd be interested in your results. I couldn't seem to nail it down.
     
    theANMATOR2b likes this.
  26. Creyke

    Creyke

    Joined:
    Oct 23, 2013
    Posts:
    2
    Really helpful post, both for moral support and for insight into the issue.
    I ended up going with the LateUpdate option which is fine for my current purposes.

    Was there ever any further information from Unity, or anywhere else for that matter, on the core issue?
     
  27. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,519
    The suggested solution was 'work around it'... to use the avatar configuration tool to rotate the bone into alignment (manual, prone to error) or fix the rig with maya/max/etc (unfriendly workflow for solo devs).

    I still find it hard to accept that as a solution since the repro case i sent confirmed there is an issue and I guess it still exists if you're seeing it today.
     
    Stephanommg likes this.
  28. Creyke

    Creyke

    Joined:
    Oct 23, 2013
    Posts:
    2
    Yep, things seems entirely unchanged form what was talked about previously in this thread.

    I was a bit unclear what was meant by this. Currently my bones have been rotated to be in a t-pose that Unity likes, in other words; all the joints are showing green in the configuration tool.

    .
     
  29. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,519
    Yeah supposedly you can configure that to rotate the bone's root position until it matches up with what you want. The other way is to adjust the rig in the authoring tool. I don't think either are valid solutions since the real issue is in Mecanim.
     
    theANMATOR2b likes this.
  30. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    There is effectively a constant rotation offset between original skeleton bone rotation and mecanim IK goal rotation.

    Goal Rotation = GR
    Skeleton Rotation = SR
    Offset Rotation = OR

    where:

    GR = SR * OR

    or:

    OR = INV(SR) * GR

    You can compute this constant offset in the 1st IK pass since IK goals are synced to skeleton at the beginning of IK solve.

    GR = GetGoalRotation(handGoalIndex)
    SR = GetBoneTransform(handIndex).rotation

    Then you can use OR to set a goal within skeleton referential using:

    SetIKGoalRotation(SomeRot*OR)

    Note: Mecanim Humanoid abstracts the original skeleton rig for many reason. One of these is to be able to create IK rigs that do not depend on original skeleton, but instead only depend a normalized humanoid rig that fits all humanoid characters.
     
  31. Benjii519

    Benjii519

    Joined:
    Oct 5, 2014
    Posts:
    1
    Was stuck on this for several days... would be great if the above information was in the Unity docs somewhere!
     
  32. MidnightCow

    MidnightCow

    Joined:
    Jun 2, 2017
    Posts:
    30
    There is a fairly simple solution to this ( just had the problem myself - hand bone rotation was offset from the IK Goal rotation ):

    1. Click on your character import inside your Project.
    2. Go to the Rig tab.
    3. Hit Configure to open up the humanoid Avatar Configuration.
    4. Find and select your hand bone in the Hierarchy view.
    5. At this point it will likely say Character is not in T-Pose in the Scene view - doesn't matter.
    6. Set the Rotation of your hand bone under Transform in the Inspector to 0 / 0 / 0.
    7. Do the same for the other hand.
    This doesn't affect any other animations as far as i can see, only the binding to the IK Goals.

    Hope that helps.
     
  33. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    This solution may not work for everybody depending on how the skinning was made or if you have child below the hands transform.
     
  34. MidnightCow

    MidnightCow

    Joined:
    Jun 2, 2017
    Posts:
    30
    Does the IK goal point towards an average or the finger/child bones or something along those lines?

    I'm guessing if you're hand is skinned so that it points visually outwards ( when in t-pose ) but the bone itself is at a different angle it would require further offset adjustment by code..
     
  35. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    No, we are working with a normalize rig, in T-pose an effector goal rotation is always (0,0,0), so mecanim compute an offset internally to remove any rotation on the wrist/ankle for the IK goal
     
    erenaydin likes this.
  36. Punfish

    Punfish

    Joined:
    Dec 7, 2014
    Posts:
    401
    I'm having the same issue. I'm using single frame animations (eg: point up, point down) then blending between them and making final adjustments with IK. But I cannot at all get the right hand to point properly at a target.
     
  37. PolarU

    PolarU

    Joined:
    Mar 14, 2017
    Posts:
    3
    3 years later and I'm running into this issue or something very similar. Haha @Punfish hello

    This is maddening.
     
  38. PolarU

    PolarU

    Joined:
    Mar 14, 2017
    Posts:
    3
  39. sgthale

    sgthale

    Joined:
    Mar 10, 2018
    Posts:
    12
    You guys should update the documentation to reflect this.