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

FBX coordinate system confusion?

Discussion in 'Editor & General Support' started by MentalFish, Dec 29, 2012.

  1. MentalFish

    MentalFish

    Joined:
    Nov 2, 2005
    Posts:
    282
    When importing an FBX from LightWave into Unity, the model comes in "backwards", even though both LightWave and Unity have left hand coordinate systems with Y as up and Z as forward.

    When importing the same FBX into left hand Cinema4D, the Z axis is not reversed, so why in Unity?

    I am wondering if it is this due to some compensation-flip/rotation that is done on import of an FBX in Unity, so that it comes in with Z as forward when exported from Maya, which is a right hand coordinate system?
     

    Attached Files:

  2. Fenrisul

    Fenrisul

    Joined:
    Jan 2, 2010
    Posts:
    618
    As a fellow lightwaver I share your pain :p

    What version of LW are you using?
    Are you using LW 11's "built in" Unity auto-import stuff?
    What FBX plugin are you using for export? (if you're using a 3rd party one)
     
  3. MentalFish

    MentalFish

    Joined:
    Nov 2, 2005
    Posts:
    282
    Hi there. I am using the built in FBX exporter in LightWave 11 as it is by far the best one available to us LightWave people. The thousand dollar question is why Unity decides to play nice with Maya's right hand coordinate system and be silly with us who are using the same left hand coordinate system as Unity. It would be nice to hear from some Unity peeps on the issue if my assumption is correct, that it is a rotational compensation done on import to play nice with Maya?
     
  4. Paulius-Liekis

    Paulius-Liekis

    Joined:
    Aug 31, 2009
    Posts:
    672
    It's hard to say why this is happening without proper investigation, but my guess is that there is something wrong with the way LW writes its FBX files (there are more known problems). The most likely reason why it works in other packages: Unity converts everything into single-pivot system (for performance reasons), and other modeling packages support multi-pivot systems (i.e. pre/post transforms), so the problem is not visible in other packages.

    Please submit your model and report as a bug and I'll try to investigate it later... Please export to FBX in ASCII format. Thanks :)
     
  5. MentalFish

    MentalFish

    Joined:
    Nov 2, 2005
    Posts:
    282
    I seriously doubt it to be a LightWave bug, since all other applications tested so far for export (3DS Max, Blender and LightWave, native files as well as FBX and OBJ) yield the same result. Only in from Maya does the 3D application's forward axis come in as forward in Unity. I only have demo versions of Cheetah and Cinema4D, so I can not test their FBX exporters.

    I have attached a new screenshot showing all the axis/orientations being reversed, apart from the ones from Maya which come in correctly. I have also attached the project folder for all to dissect.

    Could users of C4D, Cheetah, XSI and Houdini try to export an arrow like this, using the application's default forward/depth axis as the arrow's forward axis too?
     

    Attached Files:

  6. MentalFish

    MentalFish

    Joined:
    Nov 2, 2005
    Posts:
    282
    Bump. Anyone?

    How come files from left hand coordinate system 3D apps come in with a reversed forward axis in Unity, while right hand axis Maya does not? Can someone at Unity Tech. who "was there" at the beginning (OTEE), confirm or refute that Unity does this to play nice with Maya? Any and all information on this would be appreciated as I can report it directly to the LightWave FBX developer at NewTek.

    If anything it should have been the other way around with right-hand coordinate system imports having their "sideways" axis flipped, while maintaining their forward axis as forward in Unity (+Z)

    Also, could people with C4D/Cheetah/XSI/Houdini export a similar arrow, pointing down the positive Z axis to see how those apps fare when opening the FBX in Unity?
     
  7. MentalFish

    MentalFish

    Joined:
    Nov 2, 2005
    Posts:
    282
    Paulius, did you download the zip file and check out the Blender file? Forward axis in Blender != forward axis in Unity? Why is it so?
     
  8. MentalFish

    MentalFish

    Joined:
    Nov 2, 2005
    Posts:
    282
    I gots mi'self an ASCII FBX exported from C4D with Z as forward. When imported in Unity, its axis/pivot is reversed as well.
     

    Attached Files:

  9. MentalFish

    MentalFish

    Joined:
    Nov 2, 2005
    Posts:
    282
    More "evidence" to my theory on why forward Z axis in right hand coordinate system Maya == forward Z axis in left hand coordinate system Unity.

    From the Wayback Machine (2005/2006), we can see that Maya is on top of the list:
    And on the Maya info page we have this as well:
    I see that Cinema4D was supported with automatic conversion of .c4d files as well, but my gut feeling is still that the focus was to ensure "play nice with Maya" :)

    So in essence I would like to see/hear a yes or no to the theory that Unity is playing nice with Maya due to legacy decisions made back in the days when Unity was Mac only.

    If this is the case, then it is not a bug (neither in Unity or any of the non-Maya 3D apps), but a "by design" decision. I can then take the information straight to the LightWave FBX guy and see if we can have a "Z-axis rotation/flip" setting on exporting from LightWave to compensate for this rotation/flip we see in Unity. It would be nice to animate things "forward" in the 3D app, and have it also move forward in Unity. :)
     
  10. MentalFish

    MentalFish

    Joined:
    Nov 2, 2005
    Posts:
    282
    No one at Unity Tech. can answer this?
     
  11. Aurore

    Aurore

    Director of Real-Time Learning

    Joined:
    Aug 1, 2012
    Posts:
    3,106
    This could be a good idea in the interim, we are on the case though, currently in the process of doing tests with lightwave 11.5.
     
  12. MentalFish

    MentalFish

    Joined:
    Nov 2, 2005
    Posts:
    282
    Thats good to hear :)
     
  13. MentalFish

    MentalFish

    Joined:
    Nov 2, 2005
    Posts:
    282
    Any findings? Also, as you can see from the tests above, it is not really a LightWave related issue. 3DS Max, Blender, Cinema4D and so on has the same problem.
     
  14. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    I wrote a model postprocessor that automatically fixes the issue. However I'm not sure about how it deals with animation.

    Code (csharp):
    1. DELETED
    Original scene in Cinema4D


    Before postprocessing


    After postprocessing
     
    Last edited: May 27, 2013
  15. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    The code from the previous post was incorrect. It didn't support object hierarchies. This one seems to work fine:

    Code (csharp):
    1.  
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using UnityEditor;
    5. using UnityEngine;
    6.  
    7. public class Cinema4DModelPostprocessor : AssetPostprocessor
    8. {
    9.     private readonly Quaternion rotation = Quaternion.Euler(0, 180, 0);
    10.  
    11.     private void OnPostprocessModel(GameObject go)
    12.     {
    13.         var gameObjects = new List<GameObject>();
    14.         var worldPositions = new List<Vector3>();
    15.         var worldRotations = new List<Quaternion>();
    16.  
    17.         var queue = new Queue<GameObject>();
    18.         queue.Enqueue(go);
    19.  
    20.         while (queue.Any())
    21.         {
    22.             var item = queue.Dequeue();
    23.  
    24.             gameObjects.Add(item);
    25.             worldPositions.Add(item.transform.position);
    26.             worldRotations.Add(item.transform.rotation);
    27.  
    28.             foreach (Transform childTransform in item.transform)
    29.             {
    30.                 queue.Enqueue(childTransform.gameObject);
    31.             }
    32.         }
    33.  
    34.         for (int i = 0; i < gameObjects.Count; i++)
    35.         {
    36.             ApplyTransformRotation(gameObjects[i], worldPositions[i], worldRotations[i]);
    37.             ApplyGeometryRotation(gameObjects[i]);
    38.         }
    39.  
    40.         go.transform.rotation = Quaternion.identity;
    41.  
    42.     }
    43.  
    44.     private void ApplyTransformRotation(GameObject go, Vector3 position, Quaternion initialRotation)
    45.     {
    46.         go.transform.position = position;
    47.         go.transform.rotation = initialRotation * rotation;
    48.     }
    49.  
    50.     private void ApplyGeometryRotation(GameObject go)
    51.     {
    52.         var meshFilter = go.GetComponent<MeshFilter>();
    53.         if (meshFilter == null)
    54.         {
    55.             return;
    56.         }
    57.         var mesh = meshFilter.sharedMesh;
    58.         var vertices = mesh.vertices;
    59.         for (int i = 0; i < vertices.Length; i++)
    60.         {
    61.             vertices[i] = rotation * vertices[i];
    62.         }
    63.         mesh.vertices = vertices;
    64.  
    65.         var normals = mesh.normals;
    66.         for (int i = 0; i < normals.Length; i++)
    67.         {
    68.             normals[i] = rotation * normals[i];
    69.         }
    70.         mesh.normals = normals;
    71.         meshFilter.sharedMesh.RecalculateBounds();
    72.  
    73.         mesh.name = go.name;
    74.     }
    75. }
    76.  
     
    Last edited: Oct 24, 2013
  16. MentalFish

    MentalFish

    Joined:
    Nov 2, 2005
    Posts:
    282
    Interesting. I will have to give that a go. But yes, animation could be a bit of a challenge.
     
  17. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    I checked how keyframe animation works and, as I suspected, it didn't work correctly.

    Cinema 4D:
    $c.gif

    Unity:
    $u.gif

    Unfortunately, I see no way to access the keyframe data to modify it.
     
  18. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    I have just found AnimationUtility class that gives the access to the keyframes. I believe, I can fix animations too.
     
  19. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    I'm not sure, but it seems I've managed to fix animation. Guys, please, could you test this script on your models/scenes? I think, it should work both for Cinema4D and LightWave since both of them use left-handed coordinate system with Y up.

    Cinema4D <----> Unity
    $cinema.gif $unity.gif

    Code (csharp):
    1. using System.Collections.Generic;
    2. using System.Linq;
    3. using UnityEditor;
    4. using UnityEngine;
    5.  
    6. public class Cinema4DModelPostprocessor : AssetPostprocessor
    7. {
    8.     private readonly Quaternion rotation = Quaternion.Euler(0, 180, 0);
    9.  
    10.     private void OnPostprocessModel(GameObject go)
    11.     {
    12.         var gameObjects = new List<GameObject>();
    13.         var worldPositions = new List<Vector3>();
    14.         var worldRotations = new List<Quaternion>();
    15.         var queue = new Queue<GameObject>();
    16.         queue.Enqueue(go);
    17.         while (queue.Any())
    18.         {
    19.             var item = queue.Dequeue();
    20.             gameObjects.Add(item);
    21.             worldPositions.Add(item.transform.position);
    22.             worldRotations.Add(item.transform.rotation);
    23.             foreach (Transform childTransform in item.transform)
    24.             {
    25.                 queue.Enqueue(childTransform.gameObject);
    26.             }
    27.         }
    28.  
    29.         for (int i = 0; i < gameObjects.Count; i++)
    30.         {
    31.             ApplyTransformRotation(gameObjects[i], worldPositions[i], worldRotations[i]);
    32.             ApplyGeometryRotation(gameObjects[i]);
    33.             //if (gameObjects[i] != go)
    34.             {
    35.                 FixAnimation(go, gameObjects[i]);
    36.             }
    37.         }
    38.  
    39.         go.transform.rotation = Quaternion.identity;
    40.     }
    41.  
    42.     private void FixAnimation(GameObject root, GameObject go)
    43.     {
    44.         var clips = AnimationUtility.GetAnimationClips(root);
    45.         foreach (var clip in clips)
    46.         {
    47.             var curves = FindRotationCurves(clip, GetRelativePath(root, go));
    48.             if (curves != null)
    49.             {
    50.                 InvertCurve(curves[0].curve);
    51.                 InvertCurve(curves[2].curve);
    52.  
    53.                 clip.SetCurve(curves[0].path, curves[0].type, curves[0].propertyName, curves[0].curve);
    54.                 clip.SetCurve(curves[2].path, curves[2].type, curves[2].propertyName, curves[2].curve);
    55.             }
    56.  
    57.             curves = FindPositionCurves(clip, GetRelativePath(root, go));
    58.             if (curves != null)
    59.             {
    60.                 InvertCurve(curves[0].curve);
    61.                 InvertCurve(curves[2].curve);
    62.  
    63.                 clip.SetCurve(curves[0].path, curves[0].type, curves[0].propertyName, curves[0].curve);
    64.                 clip.SetCurve(curves[2].path, curves[2].type, curves[2].propertyName, curves[2].curve);
    65.             }
    66.         }
    67.     }
    68.  
    69.     private string GetRelativePath(GameObject root, GameObject child)
    70.     {
    71.         string path = "";
    72.         var transform = child.transform;
    73.         while (transform.gameObject != root)
    74.         {
    75.             if (path == "")
    76.             {
    77.                 path = transform.name;
    78.             }
    79.             else
    80.             {
    81.                 path = transform.name + "/" + path;
    82.             }
    83.             transform = transform.parent;
    84.         }
    85.         return path;
    86.     }
    87.  
    88.     private AnimationClipCurveData[] FindRotationCurves(AnimationClip clip, string path)
    89.     {
    90.         AnimationClipCurveData xCurveData = null, yCurveData = null, zCurveData = null, wCurveData = null;
    91.  
    92.         var curves = AnimationUtility.GetAllCurves(clip, true);
    93.         foreach (var curveData in curves)
    94.         {
    95.             if (curveData.path != path)
    96.             {
    97.                 continue;
    98.             }
    99.  
    100.             switch (curveData.propertyName)
    101.             {
    102.                 case "m_LocalRotation.x":
    103.                     xCurveData = curveData;
    104.                     break;
    105.                 case "m_LocalRotation.y":
    106.                     yCurveData = curveData;
    107.                     break;
    108.                 case "m_LocalRotation.z":
    109.                     zCurveData = curveData;
    110.                     break;
    111.                 case "m_LocalRotation.w":
    112.                     wCurveData = curveData;
    113.                     break;
    114.             }
    115.  
    116.             if (xCurveData != null  yCurveData != null  zCurveData != null  wCurveData != null)
    117.             {
    118.                 return new[] { xCurveData, yCurveData, zCurveData, wCurveData };
    119.             }
    120.         }
    121.  
    122.         return null;
    123.     }
    124.  
    125.     private AnimationClipCurveData[] FindPositionCurves(AnimationClip clip, string path)
    126.     {
    127.         AnimationClipCurveData xCurveData = null, yCurveData = null, zCurveData = null;
    128.  
    129.         var curves = AnimationUtility.GetAllCurves(clip, true);
    130.         foreach (var curveData in curves)
    131.         {
    132.             if (curveData.path != path)
    133.             {
    134.                 continue;
    135.             }
    136.  
    137.             switch (curveData.propertyName)
    138.             {
    139.                 case "m_LocalPosition.x":
    140.                     xCurveData = curveData;
    141.                     break;
    142.                 case "m_LocalPosition.y":
    143.                     yCurveData = curveData;
    144.                     break;
    145.                 case "m_LocalPosition.z":
    146.                     zCurveData = curveData;
    147.                     break;
    148.             }
    149.  
    150.             if (xCurveData != null  yCurveData != null  zCurveData != null)
    151.             {
    152.                 return new[] { xCurveData, yCurveData, zCurveData };
    153.             }
    154.         }
    155.  
    156.         return null;
    157.     }
    158.  
    159.     private void InvertCurve(AnimationCurve curve)
    160.     {
    161.         var keyframes = curve.keys;
    162.  
    163.         for (int i = 0; i < keyframes.Length; i++)
    164.         {
    165.             var keyframe = keyframes[i];
    166.             keyframe.value = -keyframe.value;
    167.             keyframes[i] = keyframe;
    168.         }
    169.  
    170.         curve.keys = keyframes;
    171.     }
    172.  
    173.     private void ApplyTransformRotation(GameObject go, Vector3 position, Quaternion initialRotation)
    174.     {
    175.         go.transform.position = position;
    176.         go.transform.rotation = initialRotation * rotation;
    177.     }
    178.  
    179.     private void ApplyGeometryRotation(GameObject go)
    180.     {
    181.         var meshFilter = go.GetComponent<MeshFilter>();
    182.         if (meshFilter == null)
    183.         {
    184.             return;
    185.         }
    186.  
    187.         var mesh = meshFilter.sharedMesh;
    188.         var vertices = mesh.vertices;
    189.         for (int i = 0; i < vertices.Length; i++)
    190.         {
    191.             vertices[i] = rotation * vertices[i];
    192.         }
    193.  
    194.         mesh.vertices = vertices;
    195.         var normals = mesh.normals;
    196.         for (int i = 0; i < normals.Length; i++)
    197.         {
    198.             normals[i] = rotation * normals[i];
    199.         }
    200.  
    201.         mesh.normals = normals;
    202.         meshFilter.sharedMesh.RecalculateBounds();
    203.         mesh.name = go.name;
    204.     }
    205. }
     

    Attached Files:

    Last edited: Nov 27, 2013
  20. rectalogic

    rectalogic

    Joined:
    Nov 30, 2012
    Posts:
    35
    Any plans to fix this (most interested in C4D) in Unity 5?
     
  21. Venryx

    Venryx

    Joined:
    Sep 25, 2012
    Posts:
    444
    This is the PostProcess script I use. I'm not sure if it preserves the model's animation, but it seems to work fine if you just want a static model to be oriented in Unity as it is in C4D, while keeping the root object (in both Unity and C4D) with a rotation of (0, 0, 0).

    Code (CSharp):
    1. public class ModelPostProcessor : AssetPostprocessor
    2. {
    3.     void OnPostprocessModel(GameObject root)
    4.     {
    5.         var rotaterObj = new GameObject("Rotater");
    6.         foreach (GameObject obj in root.GetComponentsInChildren<Transform>().Select(transform=>transform.gameObject))
    7.             if (obj.transform.parent == root.transform)
    8.                 obj.transform.parent = rotaterObj.transform;
    9.         rotaterObj.transform.parent = root.transform;
    10.         rotaterObj.transform.rotation = Quaternion.Euler(0, 180, 0);
    11.     }
    12. }