Search Unity

  1. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  2. Improved Prefab workflow (includes Nested Prefabs!), 2D isometric Tilemap and more! Get the 2018.3 Beta now.
    Dismiss Notice
  3. Want more efficiency in your development work? Sign up to receive weekly tech and creative know-how from Unity experts.
    Dismiss Notice
  4. Participate with students all over the world and build projects to teach people. Join now!
    Dismiss Notice
  5. Build games and experiences that can load instantly and without install. Explore the Project Tiny Preview today!
    Dismiss Notice
  6. Improve your Unity skills with a certified instructor in a private, interactive classroom. Watch the overview now.
    Dismiss Notice
  7. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

Muscle values to bone rotation

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

  1. pdinklag

    pdinklag

    Joined:
    Jan 24, 2017
    Posts:
    23
    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. SilentSin

    SilentSin

    Joined:
    Jan 3, 2013
    Posts:
    108
    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:
    23
    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:
    23
    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