Cardinal Alignment Tool

When creating a rig, the bind pose of a character mesh is a complicated topic. Modelers and TDs have an opposing set of requirements, with valid arguments on both sides.

Modelers typically desire a character mesh created in A-Pose (arms down at an angle, legs spread slightly). Stance advantages include easier normal baking, better deformation at bony landmarks, easier to critique and gauge proportions, and the underlying premise to model for the stance seen most often in motion. However, a big disadvantage is a lack of attention to armpits, crotches, and sometimes even the throat due to areas being hidden by overlapping geometry.

TDs tend to prefer a character mesh in T-pose (everything at right angles, unnaturally parallel to the world), a stance based largely on the mathematics. It is great for rigging, animation, and motion transfer but tends to mush bony landmarks if a mesh is constructed and bound in this pose. T-Pose can also present issues with certain vertex shaders when geometry crashes into itself due to the stance at bind pose.

My solution to the tug-of-war is a workflow incorporating both poses. Create, bind, and export the game mesh in A-Pose; Rig and animate the model from T-Pose. With skeletal joints created in A-Pose (the one seen the most), animations will presumably cross through bind pose most often, meaning fewer opportunities for gimbal. And since the rig is generated in T-Pose, TDs and animators enjoy the full set of rigging benefits. To top it off, bony landmarks are retained, meaning both parties are happy.

To pull this off, a bound character needs transposed from A-Pose to T-Pose. Using the OpenMaya API and some basic linear algebra, my solution involves only math. You simply extract a rotation, convert it to axes, compare those to the world to determine closest cardinal, reconstruct, and apply.


Cardinal Alignment Tool

tool development using OpenMaya and Maya 2017

The Underlying Linear Algebra

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import maya.api.OpenMaya as api2
import math
from not_included import from_vectors


def closest_cardinal(in_rotation):
    """
    Returns the euler rotation of the closest cardinal to the given input rotation. This method is used to calculate
    the rotation required to precisely axis align objects to the world. Both the input and output rotations are in
    worldspace coordinates.

    :param in_rotation:  The incoming [x, y, z] euler rotation, in worldspace
    :type in_rotation: iterable

    :return: The closest cardinal [x, y, z] euler rotation, in worldspace
    :rtype: list
    """
    # worldspace cardinal directions
    cardinal_axes = [api2.MVector(1., 0., 0.), api2.MVector(-1., 0., 0.),
                     api2.MVector(0., 1., 0.), api2.MVector(0., -1., 0.),
                     api2.MVector(0., 0., 1.), api2.MVector(0., 0., -1.)]

    # incoming rotation data
    rotate_matrix = api2.MEulerRotation([math.radians(x) for x in in_rotation]).asMatrix()
    rotate_axis_x = api2.MVector(tuple(rotate_matrix)[0:3])
    rotate_axis_y = api2.MVector(tuple(rotate_matrix)[4:7])

    # determine which cardinal axis is closest to the rotateX axis
    dots_x = [rotate_axis_x * cardinal_axis for cardinal_axis in cardinal_axes]
    closest_x = cardinal_axes.pop(dots_x.index(max(dots_x)))

    # determine which cardinal axis is closest to the rotateY axis
    dots_y = [rotate_axis_y * cardinal_axis for cardinal_axis in cardinal_axes]
    closest_y = cardinal_axes[dots_y.index(max(dots_y))]

    # then derive rotateZ via cross product
    closest_z = (closest_x ^ closest_y).normalize()

    cardinal_matrix = api2.MTransformationMatrix(from_vectors(closest_x, closest_y, closest_z))
    out_rotation = [math.degrees(x) for x in cardinal_matrix.rotation()]

    return out_rotation