Page MenuHome

FBX: Coordinate system axes exported incorrectly, resulting in a 180 degree rotation of meshes.
Closed, ResolvedPublic

Description

Blender version: 2.73a
(FBX binary exporter)

When exporting a binary FBX, the coordinate system properties [UpAxis, UpAxisSign, FrontAxis, FrontAxisSign, CoordAxis and CoordAxisSign] are chosen incorrectly.

This error arises when mapping via the look-up table RIGHT_HAND_AXES. This table is correct as documented in the comments, in that it correctly maps tuples of (Up, Front) to the corresponding FBX properties. The error arises because everywhere in the FBX exporter/importer code, the value used to index into the table is actually (Up, Forward), not (Up, Front). Note that FBX considers the "front" vector as being the vector describing the normal direction of the visible faces of the model, i.e. a vector pointing toward the camera, not away from it. Basically, the front vector is the negation of the forward vector, so the result is a coordinate system that is rotated 180 degrees to what we actually wanted.

Because of this, while the actual mesh data is correctly exported, any importer that makes use of the coordinate system properties will, on converting the mesh data, rotate it 180 degrees. (This does not affect the Blender FBX importer because it uses the same look-up table on import, so the error is reversed.)

This bug is easily illustrated by exporting any mesh and then loading it into an external viewer such as the Visual Studio 2013 built-in viewer. You will see that the mesh is rotated 180 degrees.

The fix for this is trivial: simply convert RIGHT_HAND_AXES to be indexed by (Up, Forward) instead of (Up, Front) by negating the front vectors in the table indices. I have attached before and after versions of the relevant file*. This simple change fixes the problem 100%.

Bonus info: I haven't looked too far into it but it sounds like bug T42110 may be caused by this, since it is using an external importer.

^* Hmm. My browser is giving me no indication that these files have actually been uploaded. In case they haven't, here is the before and after version of the table as text.

BEFORE:

RIGHT_HAND_AXES = {
    # Up, Front -> FBX values (tuples of (axis, sign), Up, Front, Coord).
    ('X',  'Y'):  ((0, 1),  (1, 1),  (2, 1)),
    ('X',  '-Y'): ((0, 1),  (1, -1), (2, -1)),
    ('X',  'Z'):  ((0, 1),  (2, 1),  (1, -1)),
    ('X',  '-Z'): ((0, 1),  (2, -1), (1, 1)),
    ('-X', 'Y'):  ((0, -1), (1, 1),  (2, -1)),
    ('-X', '-Y'): ((0, -1), (1, -1), (2, 1)),
    ('-X', 'Z'):  ((0, -1), (2, 1),  (1, 1)),
    ('-X', '-Z'): ((0, -1), (2, -1), (1, -1)),
    ('Y',  'X'):  ((1, 1),  (0, 1),  (2, -1)),
    ('Y',  '-X'): ((1, 1),  (0, -1), (2, 1)),
    ('Y',  'Z'):  ((1, 1),  (2, 1),  (0, 1)),
    ('Y',  '-Z'): ((1, 1),  (2, -1), (0, -1)),
    ('-Y', 'X'):  ((1, -1), (0, 1),  (2, 1)),
    ('-Y', '-X'): ((1, -1), (0, -1), (2, -1)),
    ('-Y', 'Z'):  ((1, -1), (2, 1),  (0, -1)),
    ('-Y', '-Z'): ((1, -1), (2, -1), (0, 1)),
    ('Z',  'X'):  ((2, 1),  (0, 1),  (1, 1)),
    ('Z',  '-X'): ((2, 1),  (0, -1), (1, -1)),
    ('Z',  'Y'):  ((2, 1),  (1, 1),  (0, -1)),
    ('Z',  '-Y'): ((2, 1),  (1, -1), (0, 1)),  # Blender system!
    ('-Z', 'X'):  ((2, -1), (0, 1),  (1, -1)),
    ('-Z', '-X'): ((2, -1), (0, -1), (1, 1)),
    ('-Z', 'Y'):  ((2, -1), (1, 1),  (0, 1)),
    ('-Z', '-Y'): ((2, -1), (1, -1), (0, -1)),
}

And AFTER:

RIGHT_HAND_AXES = {
    # Up, Forward -> FBX values (tuples of (axis, sign), Up, Front, Coord).
    ('X',  '-Y'):  ((0, 1),  (1, 1),  (2, 1)),
    ('X',  'Y'): ((0, 1),  (1, -1), (2, -1)),
    ('X',  '-Z'):  ((0, 1),  (2, 1),  (1, -1)),
    ('X',  'Z'): ((0, 1),  (2, -1), (1, 1)),
    ('-X', '-Y'):  ((0, -1), (1, 1),  (2, -1)),
    ('-X', 'Y'): ((0, -1), (1, -1), (2, 1)),
    ('-X', '-Z'):  ((0, -1), (2, 1),  (1, 1)),
    ('-X', 'Z'): ((0, -1), (2, -1), (1, -1)),
    ('Y',  '-X'):  ((1, 1),  (0, 1),  (2, -1)),
    ('Y',  'X'): ((1, 1),  (0, -1), (2, 1)),
    ('Y',  '-Z'):  ((1, 1),  (2, 1),  (0, 1)),
    ('Y',  'Z'): ((1, 1),  (2, -1), (0, -1)),
    ('-Y', '-X'):  ((1, -1), (0, 1),  (2, 1)),
    ('-Y', 'X'): ((1, -1), (0, -1), (2, -1)),
    ('-Y', '-Z'):  ((1, -1), (2, 1),  (0, -1)),
    ('-Y', 'Z'): ((1, -1), (2, -1), (0, 1)),
    ('Z',  '-X'):  ((2, 1),  (0, 1),  (1, 1)),
    ('Z',  'X'): ((2, 1),  (0, -1), (1, -1)),
    ('Z',  '-Y'):  ((2, 1),  (1, 1),  (0, -1)),
    ('Z',  'Y'): ((2, 1),  (1, -1), (0, 1)),  # Blender system!
    ('-Z', '-X'):  ((2, -1), (0, 1),  (1, -1)),
    ('-Z', 'X'): ((2, -1), (0, -1), (1, 1)),
    ('-Z', '-Y'):  ((2, -1), (1, 1),  (0, 1)),
    ('-Z', 'Y'): ((2, -1), (1, -1), (0, -1)),
}

Event Timeline

Rod Boyd (ib_rod) raised the priority of this task from to 90.
Rod Boyd (ib_rod) updated the task description. (Show Details)
Rod Boyd (ib_rod) edited a custom field.
Rod Boyd (ib_rod) added a comment.EditedMar 9 2015, 12:23 AM

I just noticed that my change screwed up the formatting of the code, since I made the minimal possible change and didn't adjust the whitespace. Here's a tidier version with whitespace adjusted:

AFTER:

RIGHT_HAND_AXES = {
    # Up, Forward -> FBX values (tuples of (axis, sign), Up, Front, Coord).
    ( 'X', '-Y'): ((0,  1), (1,  1), (2,  1)),
    ( 'X',  'Y'): ((0,  1), (1, -1), (2, -1)),
    ( 'X', '-Z'): ((0,  1), (2,  1), (1, -1)),
    ( 'X',  'Z'): ((0,  1), (2, -1), (1,  1)),
    ('-X', '-Y'): ((0, -1), (1,  1), (2, -1)),
    ('-X',  'Y'): ((0, -1), (1, -1), (2,  1)),
    ('-X', '-Z'): ((0, -1), (2,  1), (1,  1)),
    ('-X',  'Z'): ((0, -1), (2, -1), (1, -1)),
    ( 'Y', '-X'): ((1,  1), (0,  1), (2, -1)),
    ( 'Y',  'X'): ((1,  1), (0, -1), (2,  1)),
    ( 'Y', '-Z'): ((1,  1), (2,  1), (0,  1)),
    ( 'Y',  'Z'): ((1,  1), (2, -1), (0, -1)),
    ('-Y', '-X'): ((1, -1), (0,  1), (2,  1)),
    ('-Y',  'X'): ((1, -1), (0, -1), (2, -1)),
    ('-Y', '-Z'): ((1, -1), (2,  1), (0, -1)),
    ('-Y',  'Z'): ((1, -1), (2, -1), (0,  1)),
    ( 'Z', '-X'): ((2,  1), (0,  1), (1,  1)),
    ( 'Z',  'X'): ((2,  1), (0, -1), (1, -1)),
    ( 'Z', '-Y'): ((2,  1), (1,  1), (0, -1)),
    ( 'Z',  'Y'): ((2,  1), (1, -1), (0,  1)),  # Blender system!
    ('-Z', '-X'): ((2, -1), (0,  1), (1, -1)),
    ('-Z',  'X'): ((2, -1), (0, -1), (1,  1)),
    ('-Z', '-Y'): ((2, -1), (1,  1), (0,  1)),
    ('-Z',  'Y'): ((2, -1), (1, -1), (0, -1)),
}

Hrmmm… already changed that table a few times in the past. :/

Do you have a good reference link explaining what exactly FBX expects here? I never could fine one, so just tried guessing so far.

Bastien Montagne (mont29) lowered the priority of this task from 90 to Normal.
Bastien Montagne (mont29) updated the task description. (Show Details)
Rod Boyd (ib_rod) added a comment.EditedMar 9 2015, 11:11 AM

The best I have is from the comments in the FBX SDK source code. The following lines are of interest (included again with more context below):

From fbxsdk_2015.1\include\fbxsdk\scene\FbxAxisSystem.h:

[Line 61] There still needs a parameter to denote the direction of the EFrontVector just as the EUpVector. And the sign of the
[Line 62] EFrontVector represents the direction (1 is front and -1 is back relative to observer).
...
[Line 110] EFrontVector Vector with origin at the screen pointing toward the camera.

As you can see, while it may not be the clearest of descriptions, they definitely specify that the vector points toward the camera. I believe this is why they prefer the term "front" (as in front-facing polygons) rather than, say, "forward".

I can also tell you from my own experience that this is consistent with what I've seen from Autodesk's own exporters for Maya, Max, etc. Our import code works fine with files generated by those exporters. Only with Blender (2.73a) do I see the mesh rotated 180 degrees. On investigation I found that the mesh data itself is correct, but that the coordinate system conversion (converting from the system used by the data in the file to the system used internally by our renderer) was rotating it. This occurred even if I export the mesh in the same coordinate system as our renderer uses internally, which means that the conversion should be skipped altogether. Eventually I tracked this down to the coordinate system properties, which were flipped compared to what they should be for those files according to the comments above. This change fixed it for us. I can now export with any coordinate system axes and have the data import correctly, and the same code works for FBX files generated by the other exporters. I also verified it by viewing the files in the FBX viewer in Visual Studio 2013. Prior to the change the mesh is rotated 180 degrees no matter which coordinate system axes I export as. With the change the mesh appears correctly oriented.

.....

As promised here are those comments with more surrounding context:

fbxsdk_2015.1\include\fbxsdk\scene\FbxAxisSystem.h, beginning at line 53:

    The EFrontVector specifies which axis has the front and back direction in the system. It is not an independent variable, 
	which means it depends on EUpVector. The enum values ParityEven and ParityOdd denote the first one and 
	the second one of the remain two axes in addition to the up axis.
    
    For example if the up axis is X, the remain two axes will be Y And Z, so the ParityEven is Y, and the ParityOdd is Z
    ; If the up axis is Y, the remain two axes will X And Z, so the ParityEven is X, and the ParityOdd is Z; 
    If the up axis is Z, the remain two axes will X And Y, so the ParityEven is X, and the ParityOdd is Y. 
    
    There still needs a parameter to denote the direction of the EFrontVector just as the EUpVector. And the sign of the 
    EFrontVector represents the direction (1 is front and -1 is back relative to observer).

fbxsdk_2015.1\include\fbxsdk\scene\FbxAxisSystem.h, beginning at line 110:

    /** \enum EFrontVector  Vector with origin at the screen pointing toward the camera.
      *                     This is a subset of enum EUpVector because axis cannot be repeated.
	  *                     We use the system of "parity" to define this vector because its value (X,Y or Z axis)
	  *						really depends on the up-vector. The EPreDefinedAxisSystem list the up-vector, parity and
	  *                     coordinate system values for the predefined systems.
	  * \see Detailed description of FbxAxisSystem.
      */

Yeah, that kind of documentation can pretty much be understood in both directions :|

But still, think your explanations make sense, and quick check indeed shows rotated issue with UE4 (much easier to test it myself now that UE4 can be built on Linux!).

Will commit, a huge thanks for finding and fixing this!