Consider the following armature:
Two important notes: first, I'm using local space for posing, and two, the arm has zero bone roll. When I attempt to rotate the arm and restrict to the X, Y, and Z axes, the axes are as follows. Call this image Axes A (for easier reference later).
This all makes perfect sense so far. Next, I move the tail of the arm down, as shown here:
In doing so, Blender automatically rolls the arm (in this case, by 27 degrees). These are the resulting X, Y, and Z axes. As expected, X is unaffected, while Y and Z roll forward. Call this image Axes B.
Finally, I reset the arm's roll to zero. Bizarrely, doing this appears to rotate the arm back around its local Y axis.
These are the local XYZ axes for the arm now (shown from a different angle to emphasize that they've become skewed). Call this image Axes C.
I'm using Blender to export meshes, armatures, and actions (animations) for video game development. As part of that process, my engine must be able to compute accurate rotation matrices per bone, where "accurate" means "matching Blender". That problem, then, is the crux of my question here: how does Blender compute local rotation axes for bones?
The approach I'm currently using is as follows:
- Compute each bone's axis (i.e. normalized vector from head to tail).
- Compute a quaternion rotation from unit Y (i.e. [0, 1, 0]) to that axis. This effectively rotates the XYZ coordinate space.
- Multiply that quaternion by another "roll" quaternion, computed with
This approach has worked correctly for many bones, but fails for angled bones like the arm shown in Axes B and Axes C. The reason is that, by the steps above, the angled arm with zero roll should result in Axes B (X axis unaffected, with Y and Z rolled forward). The only way to achieve the axes shown in Axes C would be to roll the coordinate space, which is why I'm baffled as to how Blender uses those axes while the arm's roll is supposedly zero.
I even investigated Blender's C source code directly. Specifically, I adapted the
vec_roll_to_mat3_normalized function from armature.c. Using that function actually did exactly what I described in the previous paragraph: the arm's local axes matched Axes B, not Axes C. Of course it's possible I'm referencing the wrong function, but Blender's current behavior doesn't even make sense to me conceptually (why does Blender assign the arm in Axes B a roll 27° when it hasn't actually rolled around its local axis?).
Hoping for some clarification on this topic.
It was suggested that I enable the "Axes" checkbox (under Object Data Properties -> Viewport Display). As shown below, this visualization shows the same axes from my examples above.