Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Muscle values to bone rotation

Discussion in 'Animation' started by pdinklag, Oct 8, 2018.

  1. pdinklag

    pdinklag

    Joined:
    Jan 24, 2017
    Posts:
    151
    I am currently exploring uncharted territories of Mecanim. My goal is to implement my own IK solver via Mecanim muscle / DoF values, but getting and setting poses via a HumanPoseHandler is rather slow if done many times a frame. Thus, I'd like to simulate the behaviour of Mecanim on a lightweight, virtual skeleton instead. But don't let me bore you with my reasons here and cut to the chase... :)

    I'd like to know precisely how Mecanim computes a bone's rotation from the corresponding muscle / DoF values (which are usually in the [-1,1] range)?

    With some insight into the C# reference (especially here), I have worked out this code snippet that works under some circumstances:
    Code (CSharp):
    1. public Quaternion BoneRotation(Avatar avatar, int bone, float x, float y, float z) {
    2.     var mx = HumanTrait.MuscleFromBone(bone, 0);
    3.     var my = HumanTrait.MuscleFromBone(bone, 1);
    4.     var mz = HumanTrait.MuscleFromBone(bone, 2);
    5.  
    6.     var preQ = avatar.GetPreRotation(bone); // exposed via reflection
    7.     var sign = avatar.GetLimitSign(bone); // exposed via reflection
    8.  
    9.     var r = Quaternion.Euler(
    10.        (mx >= 0) ? (MuscleAngle(mx, x) * sign.x) : 0.0f,
    11.        (my >= 0) ? (MuscleAngle(my, y) * sign.y) : 0.0f,
    12.        (mz >= 0) ? (MuscleAngle(mz, z) * sign.z) : 0.0f);
    13.  
    14.     return parentRotation * preQ * r;
    15. }
    Calculating the individual DoF angles (MuscleAngle) from the muscle values is easy once you got the limits from the avatar settings. However, the calculated result is correct only if at most one DoF receives a non-zero value (e.g., x=0,y=0,z=1). As soon as multiple DoF angles are set (e.g., x=0,y=1,z=1), my result is wrong and while it appears that some post transformation is missing, for the hell of it, I cannot figure out what that would be.

    So how does Mecanim combine the single DoF rotations into the bone's final rotation?
     
  2. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,554
    Could be that it uses a different rotation order.

    Instead of a single Quaternion.Euler call, try doing a separate AngleAxis call for each axis and multiplying them in different orders.
     
  3. pdinklag

    pdinklag

    Joined:
    Jan 24, 2017
    Posts:
    151
    I tried all 6 now and even with the axes pre-rotated based on the parent rotation, no success there.
     
  4. Peter-K

    Peter-K

    Joined:
    Apr 12, 2017
    Posts:
    3
    I'm afraid I currently do not know why your calculation is sometimes off, but still would like to tune in to the muscle space to rotation conversion discussion.
    In particular, I wonder how you retrieved joint rotation limit data. In my project, the minimum and maximum rotation are manually computed right after instantiating the Avatar and before further changing its muscle values, like in this example:

    Code (CSharp):
    1. Animator animator = GetComponent<Animator>();
    2. HumanPoseHandler humanPoseHandler = new HumanPoseHandler(GetComponent<Animator>().avatar, transform);
    3. int leftEyeInOutMuscleIndex = HumanTrait.MuscleFromBone((int)HumanBodyBones.LeftEye, 1);
    4. Transform leftEye = animator.GetBoneTransform(HumanBodyBones.LeftEye);
    5. Quaternion leftEyeTaredRotation = leftEye.rotation;
    6. HumanPose currentPose = new HumanPose();
    7. humanPoseHandler.GetHumanPose(ref currentPose);
    8. HumanPose destinationPose = currentPose;
    9.  
    10. // Out Limit
    11. destinationPose.muscles[leftEyeInOutMuscleIndex] = -1f;
    12. _humanPoseHandler.SetHumanPose(ref destinationPose);
    13. Quaternion leftEyeMaxOutRotation = leftEye.rotation;
    14. float leftEyeMaxOutLimit = -Quaternion.Angle(leftEyeTaredRotation, leftEyeMaxOutRotation);
    15.  
    16. // In Limit
    17. destinationPose.muscles[leftEyeInOutMuscleIndex] = 1f;
    18. humanPoseHandler.SetHumanPose(ref destinationPose);
    19. Quaternion leftEyeMaxInRotation = leftEye.rotation;
    20. float leftEyeMaxInLimit = Quaternion.Angle(leftEyeTaredRotation, leftEyeMaxInRotation);
    Did you resolve that issue with reflection as well?
     
  5. pdinklag

    pdinklag

    Joined:
    Jan 24, 2017
    Posts:
    151
    Well I do know why - it's because the axes rotations somehow depend on each other, much like @SilentSin suggested. But it's not a simple re-ordering of the rotations, and I cannot figure out how exactly they transform each other. Good opportunity to bump this. :D

    Nope, but that's a cool idea for sure! :)

    Right now, I am using a copy of the original avatar data - via the asset's ModelImporter, you can retrieve a HumanDescription, which also contains the joint limits. However, for distribution, the data needs to be copied at edit time and serialized to another object, because the ModelImporter is Editor-only. That's quite a bloated workflow and I might just prefer yours!

    There's no way to get the limits directly from the avatar as far as I know, which should be changed IMO. The data has to be there, it's just not exposed to C# in any way (probably because Mecanim was never meant to be used in this immediate way).
     
    Last edited: Oct 19, 2018
    awesomedata likes this.
  6. awesomedata

    awesomedata

    Joined:
    Oct 8, 2014
    Posts:
    1,419
    I wonder if an extra internal processing step is taking place between frames by Mecanim that is messing up your calculations.

    Maybe @Mecanim-Dev has some insight on what causes this and the correct way to approach it?
     
  7. ecurtz

    ecurtz

    Joined:
    May 13, 2009
    Posts:
    640
    Has anyone made any progress on this?

    It is possible (although kind of ugly) to retrieve the limits directly from the avatar using serialized properties.

    It definitely isn't the problem, but I'm guessing you should probably also be multiplying by postQ in your final result.
     
  8. lox9973

    lox9973

    Joined:
    Nov 22, 2019
    Posts:
    1
    I have figured it out. It's using swing-twist degrees. @pdinklag
    Code (CSharp):
    1. void SetMuscle(HumanBodyBone humanBodyBone, Vector3 muscle) {
    2.     var sign = avatar.GetLimitSign(humanBodyBone);
    3.     var preQ = avatar.GetPreRotation(humanBodyBone);
    4.     var postQ = avatar.GetPostRotation(humanBodyBone);
    5.     var scale = new Vector3( // TODO: use GetMuscleDefaultMin if muscle is negative
    6.         HumanTrait.GetMuscleDefaultMax(HumanTrait.MuscleFromBone(humanBodyBone, 0)),
    7.         HumanTrait.GetMuscleDefaultMax(HumanTrait.MuscleFromBone(humanBodyBone, 1)),
    8.         HumanTrait.GetMuscleDefaultMax(HumanTrait.MuscleFromBone(humanBodyBone, 2)));
    9.     var angle = Vector3.Scale(sign, Vector3.Scale(scale, muscle));
    10.     animator.GetBoneTransform(humanBodyBone).localRotation =
    11.         preQ
    12.         * Quaternion.AngleAxis(new Vector3(0, angle.y, angle.z).magnitude, new Vector3(0, angle.y, angle.z).normalized)
    13.         * Quaternion.AngleAxis(angle.x, new Vector3(1,0,0))
    14.         * Quaternion.Inverse(postQ);
    15. }
     
    dgoyette and jameslroll like this.
  9. Rukhanka

    Rukhanka

    Joined:
    Dec 14, 2022
    Posts:
    204
    Hi. Need to resurrect this old thread, but code provided by @lox9973 is not fully correct. It can be easly observed by comparing Unity's default humanoid avatar muscle editor window manipulation and manipulation by skeleton bones by this script. Rotation around X axis is correct, but ZY combination are become wrong when approaching to limit muscle values. So question is follows: how to make quaternion that rotates bone using muscle values (euler angles) exactly like Unity does?
     
  10. BAMFOO

    BAMFOO

    Joined:
    Jan 23, 2024
    Posts:
    1
    Before that, anyone could tell me that what postQ and preQ exactly mean?:confused: