Search Unity

  1. Unity 2019.1 is now released.
    Dismiss Notice

Muscle values to bone rotation

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

  1. pdinklag


    Jan 24, 2017
    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);
    6.     var preQ = avatar.GetPreRotation(bone); // exposed via reflection
    7.     var sign = avatar.GetLimitSign(bone); // exposed via reflection
    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);
    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. SilentSin


    Jan 3, 2013
    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


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


    Apr 12, 2017
    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;
    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);
    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


    Jan 24, 2017
    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


    Oct 8, 2014
    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


    May 13, 2009
    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.