Search Unity

Mesh.bindPoses

Discussion in 'Documentation' started by Baste, Feb 3, 2016.

  1. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,336
    The second sentence of the description of this method is:

    "The bind pose is the inverse of inverse transformation matrix of the bone, when the bone is in the bind pose."

    Which isn't really English. I believe that the second "inverse" should be "the".

    The page needs a better explanation too - I'm in the process of figuring out exactly how bind poses work, and the description doesn't get me any closer, even with the spelling error sorted out.
     
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,336
    Addendum: the example from the page doesn't actually work, as Unity will throw errors if you try it. It's easy to fix - just set the generated animation's legacy variable to true.
     
  3. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Agree that this page (link) needs work. I tried to revert the bindposes back a few months ago but failed because of the sparse docs and my limited matrix knowledge. I'm eager to pick that up again when you'd fix this:)
     
  4. duck

    duck

    Unity Technologies

    Joined:
    Oct 21, 2008
    Posts:
    358
    This page is now updated online. The mistake in the sentence is fixed, the example is fixed, and is now present in c# and JS (it was JS only, previously). :)
     
    Seneral likes this.
  5. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Thanks, example does work now! :)
    An example to revert the bindpose inversion would be great though. Basically, is it possible to retrieve the initial transform (without animations applied) from this value?

    Edit: Nice, I think I understood it. I managed to always restore the initial local bone position from the bindposes correctly like this:
    Code (csharp):
    1.  
    2. // Recreate the local transform matrix of the bone
    3. Matrix4x4 localMatrix = bindPose.inverse;
    4. // Recreate local transform from that matrix
    5. boneTrans.localPosition = localMatrix.MultiplyPoint (Vector3.zero);
    6. boneTrans.localRotation = Quaternion.LookRotation (localMatrix.GetColumn (2), localMatrix.GetColumn (1));
    7. boneTrans.localScale = new Vector3 (localMatrix.GetColumn (0).magnitude, localMatrix.GetColumn (1).magnitude, localMatrix.GetColumn (2).magnitude);
    8.  
    This sets the bone to it's rest pose, no matter how both the local and parent trans were transformed:)
     
    Last edited: Feb 28, 2016
    Baste likes this.
  6. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    @duck
    I seem to get some problems regarding the bone transformations. Maybe you can help out there, because it seems the bindPoses for bones in actual rigs are created differently than what the examples propose.
    I created some debugs for a sample bone here, hope the labels are easy to understand:
    Screenshot6.jpg
    So I somewhat got the correct position by calculating the bind pose minus the parent bind pose, like that:
    Code (csharp):
    1. (bindPose * parentBindPose.inverse).inverse
    And the following for the root bone:
    Code (csharp):
    1. bindPose.inverse
    Then I can decode the local position like above.
    Can you explain why that works? Can you provide the exact formula on how the bindPoses are calculated?
     
    Last edited: Mar 5, 2016
  7. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Alas, not for me. I tried to apply it to a whole mesh like this:

    Code (CSharp):
    1.     [ContextMenu("Reset to Bind Pose")]
    2.     public void ResetBindPose() {
    3.         Mesh mesh = meshToReset.sharedMesh;
    4.         Transform[] bones = meshToReset.bones;
    5.         for (int i=0; i<bones.Length; i++) {
    6.             Transform boneTrans = bones[i];
    7.             Matrix4x4 bindPose = mesh.bindposes[i];
    8.            
    9.             // Recreate the local transform matrix of the bone
    10.             Matrix4x4 localMatrix = bindPose.inverse;
    11.             // Recreate local transform from that matrix
    12.             boneTrans.localPosition = localMatrix.MultiplyPoint (Vector3.zero);
    13.             boneTrans.localRotation = Quaternion.LookRotation (localMatrix.GetColumn (2), localMatrix.GetColumn (1));
    14.             boneTrans.localScale = new Vector3 (localMatrix.GetColumn (0).magnitude, localMatrix.GetColumn (1).magnitude, localMatrix.GetColumn (2).magnitude);
    15.         }
    16.         Debug.Log("Reset " + bones.Length + " bones to bind pose (hopefully)");
    17.     }
    ...and it made mincemeat out of my poor mesh. Did I misapply your code somehow?
     
  8. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Ok I'm not into the topic anymore I have to admit. But what I would assume from my follow-up message is that this applies only for the root bone. So you would have to set localMatrix like this:
    Code (csharp):
    1. localMatrix = boneTrans.parent!=null? (bindPose * parentBindPose.inverse).inverse : bindPose.inverse;
    I'll check into my old project how I did it...
     
  9. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    @JoeStrout Actually, I found what I was working back then. Just excluded other WIP stuff, as it was in the context of a whole character system I put on hold for a while. I attached the PoseManager, it basically can store the state of a complete rig and save it, then recall it at any time. It can also restore the rig to it's rest pose (which was my original use case), as that information is always stored in the bindpose.
    Structure may be debatable though, as I made each rigpose a scriptable object you have to save as an asset...
    Hope it works for you, too! :)

    EDIT: Created a Github Gist
     

    Attached Files:

    Last edited: Jun 20, 2017
    TRuoss likes this.
  10. radiatoryang

    radiatoryang

    Joined:
    Jul 7, 2014
    Posts:
    19
    The code here helped me put together my own solution... I'm trying to reset the bind pose on a Mixamo Fuse model, which is made of several different skinned mesh renderers, and each SMR had a different part of the complete bind pose, so I had to search through all of them before actually doing the transform adjustments... here's my code if it helps anyone else:

    Code (CSharp):
    1.     // based on some code by JoeStrout https://forum.unity3d.com/threads/mesh-bindposes.383752/
    2.     public void RestoreBindPose () {
    3.         // if you're using Mixamo models, every SkinnedMeshRenderer has a fraction of the full bind pose data...
    4.         // so first we have to search through all of the SMRs and combine all the bindpose data into a dictionary
    5.         var    smRenderers2 = transform.parent.GetComponentsInChildren<SkinnedMeshRenderer>();
    6.  
    7.         Dictionary<Transform, Matrix4x4> bindPoseMap = new Dictionary<Transform, Matrix4x4>();
    8.  
    9.         foreach ( var smr in smRenderers2 ) {
    10.             for( int i=0; i<smr.bones.Length; i++) {
    11.                 if ( !bindPoseMap.ContainsKey( smr.bones[i] ) ) {
    12.                     bindPoseMap.Add( smr.bones[i], smr.sharedMesh.bindposes[i] );
    13.                 }
    14.             }
    15.         }
    16.  
    17.         // based on data, now move the bones based on the bindPoseMap
    18.         foreach( var kvp in bindPoseMap ) {
    19.             Transform boneTrans = kvp.Key;
    20.             Matrix4x4 bindPose = kvp.Value;
    21.  
    22.             // Recreate the local transform matrix of the bone
    23.             Matrix4x4 localMatrix = bindPoseMap.ContainsKey(boneTrans.parent) ? (bindPose * bindPoseMap[boneTrans.parent].inverse).inverse : bindPose.inverse;
    24.             // Recreate local transform from that matrix
    25.             boneTrans.localPosition = localMatrix.MultiplyPoint (Vector3.zero);
    26.             boneTrans.localRotation = Quaternion.LookRotation (localMatrix.GetColumn (2), localMatrix.GetColumn (1));
    27.             boneTrans.localScale = new Vector3 (localMatrix.GetColumn (0).magnitude, localMatrix.GetColumn (1).magnitude, localMatrix.GetColumn (2).magnitude);
    28.         }
    29.         Debug.Log("Reset " + bindPoseMap.Count + " bones to bind pose");
    30.     }
     
  11. Seneral

    Seneral

    Joined:
    Jun 2, 2014
    Posts:
    1,206
    Good improvement!
    I've had my own setup that unified the SMR bone references of the mixamo characters so I did not experience that 'problem':)
     
    Last edited: Feb 9, 2017
  12. tinyant

    tinyant

    Joined:
    Aug 28, 2015
    Posts:
    127
    You can use reflection to call Unity's restore to Bind Pose method
    Code (CSharp):
    1.     private void ReflectionRestoreToBindPose()
    2.     {
    3.         if (_target == null)
    4.             return;
    5.         Type type = Type.GetType("UnityEditor.AvatarSetupTool, UnityEditor");
    6.         if (type != null)
    7.         {
    8.             MethodInfo info = type.GetMethod("SampleBindPose", BindingFlags.Static | BindingFlags.Public);
    9.             if (info != null)
    10.             {
    11.                 info.Invoke(null, new object[] { _target });
    12.             }
    13.         }
    14.     }
     
    Seneral and Baste like this.
  13. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    1,235
    I am having trouble with this. I am using the Unity.Recorder to recreate some animations and remove root motion from them and If I go in manually to the model in the configure tab and hit Sample Bind-Pose and then apply, the end result after going through and removing the root Z axis comes out as it should. If I try to run any of the above methods in place of doing it manually, the outcome ends up being that the animation comes out as if I had not done it.

    I attempted to alter another script I found on here which allows for changing the Avatar to Human via a similar method, but I just keep ending up with "Setting the parent of a transform which resides in a Prefab Asset is disabled to prevent data corruption."

    Here was the original I found:

    Code (CSharp):
    1.         public static void MakeHumanAvatar(string avatarPath, bool reimportModel = true)
    2.         {
    3.             var aEditorType = typeof(Editor).Assembly.GetType("UnityEditor.AvatarEditor");
    4.             var editor = Editor.CreateEditor(AssetDatabase.LoadAssetAtPath<Avatar>(avatarPath), aEditorType);
    5.  
    6.             var nonPublicInstance = BindingFlags.Instance | BindingFlags.NonPublic;
    7.  
    8.             aEditorType.GetMethod("SwitchToEditMode", nonPublicInstance).Invoke(editor, null);
    9.  
    10.             var mapperField = aEditorType.GetField("m_MappingEditor", nonPublicInstance);
    11.             var mapperType = mapperField.FieldType;
    12.             var mapper = mapperField.GetValue(editor);
    13.  
    14.             mapperType.GetMethod("PerformAutoMapping", nonPublicInstance).Invoke(mapper, null);
    15.             mapperType.GetMethod("MakePoseValid", nonPublicInstance).Invoke(mapper, null);
    16.             mapperType.GetMethod("Apply", nonPublicInstance).Invoke(mapper, null);
    17.  
    18. #if UNITY_2017_1_OR_NEWER && !UNITY_2018_1_OR_NEWER
    19.             aEditorType.GetMethod("SwitchToAssetMode", nonPublicInstance).Invoke(editor, null);
    20. #endif
    21. #if UNITY_2018_1_OR_NEWER
    22.             aEditorType.GetMethod("SwitchToAssetMode", nonPublicInstance).Invoke(editor, new object[] { false });
    23. #endif
    24.             if (!reimportModel) return;
    25.  
    26.             var modelImporter = AssetImporter.GetAtPath(avatarPath) as ModelImporter;
    27.             if (modelImporter != null) modelImporter.SaveAndReimport();
    28.         }

    Here was my attempted alteration, but I think there is more to it that I am just not sure about:

    Code (CSharp):
    1.         public static void ForceBindPose(string path, bool reimportModel = true)
    2.         {
    3.             var asset = AssetDatabase.LoadAssetAtPath<GameObject>(path);
    4.          
    5.             Type type = Type.GetType("UnityEditor.AvatarSetupTool, UnityEditor");
    6.             if (type != null)
    7.             {
    8.                 MethodInfo info = type.GetMethod("SampleBindPose", BindingFlags.Static | BindingFlags.Public);
    9.                 if (info != null)
    10.                     info.Invoke(null, new object[] { asset });
    11.             }
    12.             if (!reimportModel) return;
    13.  
    14.             var modelImporter = AssetImporter.GetAtPath(path) as ModelImporter;
    15.             if (modelImporter != null) modelImporter.SaveAndReimport();
    16.         }
     
  14. keenan-reimer

    keenan-reimer

    Joined:
    Jun 16, 2017
    Posts:
    1
    I wrestled with this issue until I realized that ALL of the poses in
    smr.sharedMesh.bindposes
    are relative to the root bone AND inverted.

    This code resets the pose of a skinned mesh renderer without moving the root bone:
    Code (CSharp):
    1. SkinnedMeshRenderer smr = providedByContext();
    2. for (int i = 0; i < smr.bones.Length; i++) {
    3.   Matrix4x4 wBone = smr.rootBone.localToWorldMatrix * smr.sharedMesh.bindposes[i].inverse;
    4.   smr.bones[i].position = wBone.MultiplyPoint3x4(Vector3.zero);
    5.   smr.bones[i].rotation = wBone.rotation;
    6. }
     
    tonytopper and JoeStrout like this.
  15. pdinklag

    pdinklag

    Joined:
    Jan 24, 2017
    Posts:
    154
    customphase likes this.