Search Unity

SampleAnimation() is wrong???

Discussion in 'Animation' started by CloudyVR, Aug 7, 2019.

  1. CloudyVR

    CloudyVR

    Joined:
    Mar 26, 2017
    Posts:
    715
    I have just imported a character created from Blender, there are no animations other than a restPose (where all keyframes are set to their identities).

    The character is already in the RestPose when imported into Unity, but I have a special need to also sample some of the animations.

    AnimationClip.SampleAnimation

    When I sample the RestPose I see that the character twists up slightly???


    I was not expecting the character to change at all, because it already is in restpose,

    Where is this twist coming from? It is causing major misalignments with my project. I can not find same inconsistency in the Blender file so this must be coming from Unity's end.

    Please help me figure this out (this has been costing days of wasted effort).

    [edit]

    If I change rig type to "generic" then I am able to sample any animation and they look perfectly spot on.

    Why would generic/humanoid make any difference?

    How can I sample my humanoid as a generic?
     
    Last edited: Aug 7, 2019
  2. CloudyVR

    CloudyVR

    Joined:
    Mar 26, 2017
    Posts:
    715
    This was not simple to achieve at all and Unity made this extremely difficult.

    But I was able to hack around the limitation with the aid of an AssetPostprocessor.

    Basically as I mentioned before "If I change rig type to "generic" then I am able to sample any animation and they look perfectly spot on". So that's exactly what I am doing.

    Here's some sudo code taken directly from my asset postprocessor. Notice how I first import as generic rig, then sample all reference poses with SampleAnimation(), finally I set the reimportTag tag in the userData and re-import as a humanoid rig:
    Code (CSharp):
    1. public class MyAssetPostprocessor : AssetPostprocessor {
    2.  
    3.  
    4.     private const string reimportTag = "REIMPORT";
    5.  
    6.     private void OnPreprocessModel() {
    7.         //Load in the preconfigured avatar from the resources folder
    8.  
    9.         string templateType = CheckIsTemplate (templates);
    10.         if (!String.IsNullOrEmpty(templateType)) {
    11.  
    12.             var importer = (ModelImporter) assetImporter;
    13.  
    14.  
    15.             if (Path.GetExtension (assetPath) != ".fbx") {
    16.                 importer.animationType = ModelImporterAnimationType.None;
    17.                 return;
    18.             }
    19.  
    20.             if (!importer.userData.Contains (reimportTag)) {
    21.                 if (importer.animationType != ModelImporterAnimationType.Human) { //if the type is ever changed from human, then invalidate userdata so it will force everythin to be re-run
    22.                     importer.userData = ""; //reset the user data
    23.                     Debug.Log ("RESET USERDATA");
    24.                 }
    25.             }
    26.  
    27.             importer.isReadable = true;
    28.  
    29.             importer.importAnimation = true;
    30.  
    31.             importer.importMaterials = true;
    32.             importer.materialLocation = ModelImporterMaterialLocation.External;
    33.  
    34.             //Blender's normals are wrong when imported into unity causing artifacts in whole body when using blendshapes, use calculate to force unity to make the normals correct.
    35.             importer.importNormals = ModelImporterNormals.Calculate;
    36.             importer.importBlendShapeNormals = ModelImporterNormals.Calculate;
    37.  
    38.  
    39.             if (importer.userData.Contains (reimportTag)) {
    40.                 importer.animationType = ModelImporterAnimationType.Human; //The rest pose has already been processed and we are re-importing as a humanoid character now
    41.             } else {
    42.                 importer.animationType = ModelImporterAnimationType.Generic; //THIS LINE IS CRITIAL FOR SAMPLING A PROPER REST POSE. MUST FIRST IMPORT AS GENERIC!!
    43.             }
    44.         }
    45.     }
    46.  
    47.  
    48.     public static GameObject CreateRestPoseRig(GameObject baseRig, string assetPath) {
    49.         var animator = baseRig.transform.root.GetComponentInChildren<Animator> ();
    50.  
    51.         var assetRepresentationsAtPath = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetPath);
    52.         foreach (var assetRepresentation in assetRepresentationsAtPath) {
    53.             var animationClip = assetRepresentation as AnimationClip;
    54.  
    55.             if (animationClip != null) {
    56.                 if (animationClip.name.Contains ("RestPose")) { //Found the rest pose created by the blend script, this is great!
    57.                     animationClip.SampleAnimation (animator.gameObject, 0); //evaluate the restPose to pose this character before copying it as the restPose restPoseRig
    58.                 }
    59.             }
    60.         }
    61.  
    62.         GameObject restPoseRig = GameObject.Instantiate (baseRig, baseRig.transform);//Make a copy of the armature
    63.         restPoseRig.SetActive (false); //this rig is a reference
    64.  
    65.         ...
    66.  
    67.         ...
    68.         return restPoseRig;
    69.     }
    70.  
    71.  
    72.     static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
    73.     {
    74.         foreach (string assetPath in importedAssets)
    75.         {            
    76.             if (Path.GetExtension (assetPath) == ".meta") {
    77.                 continue;
    78.             }
    79.  
    80.             if (assetPath.StartsWith (processingFolder)) {//Looking for the copied .fbx file that resides in the projects processing/prefab folder
    81.  
    82.                 if (!assetPath.EndsWith (".fbx", StringComparison.OrdinalIgnoreCase)) {//only allow the .fbx files in the processing folder
    83.                     continue;
    84.                 }
    85.                 GameObject modelAsset = AssetDatabase.LoadAssetAtPath<GameObject> (assetPath); //LOADING AN ASSET
    86.                 ModelImporter modelImporter = ModelImporter.GetAtPath (assetPath) as ModelImporter;
    87.  
    88.  
    89.                 //ENFORCE REST-POSE SECTION (FIRST IMPORT - CHARACTER TYPE == GENERIC):
    90.  
    91.                 if (!modelImporter.userData.Contains (reimportTag)) { //the reimportTag has not been set, this means to create the rest pose rig now
    92.                     GameObject modelGeneric = (GameObject)PrefabUtility.InstantiatePrefab(modelAsset);
    93.                     GameObject realGeneric = GameObject.Instantiate(modelGeneric); //this is a game object that we can re-arange and change parenting or objects, then save as the original prefab later on
    94.                     var armatureGeneric = realGeneric.transform.root.GetComponentInChildren<Animator>();
    95.                     armatureGeneric.name = modelGeneric.name; //remove "(clone) or any other discrepancies from name"
    96.  
    97.                     GameObject.DestroyImmediate (modelGeneric); //destroy the prefab as it will be overwritten by "real"
    98.  
    99.                     var restPose = CreateRestPoseRig(realGeneric, assetPath); //this is a game object that we can re-arange and change parenting or objects, then save as the original prefab later on;
    100.  
    101.                     foreach (var transform in restPose.GetComponentsInChildren<Transform>()) {
    102.                         transform.name = "RestPoseRig_" + transform.name; //must rename each bone so that Unity's seriously bugged animator does not attempt to use them instead of the actual character!
    103.                     }
    104.  
    105.                     string modelFileNameGeneric = Path.GetFileNameWithoutExtension( assetPath );
    106.                     string destinationPathGeneric = Path.Combine(prefabsFolder, modelFileNameGeneric + ".prefab");
    107.  
    108.                     PrefabUtility.SaveAsPrefabAsset (restPose, destinationPathGeneric);
    109.  
    110.                     GameObject.DestroyImmediate (realGeneric);
    111.  
    112.                     modelImporter.userData = modelImporter.userData + " " + reimportTag; //Add reimportTag tag to change the character type to ModelImporterAnimationType.Human
    113.                     modelImporter.SaveAndReimport ();
    114.                     return;
    115.                 }
    I think SampleAnimation is broken (for humanoids), Maybe if a "SampleAnimationGeneric()" method existed to avoid the twisting, it would be very useful!
     
    Last edited: Aug 8, 2019