Introduction to Matrices in Maya

February 02, 2024 | GitHub


Overview

Maya offers extensive capabilities for artists and technical experts, significantly enhanced by its API for custom tool and plugin development. However, grasping matrices—a core component of 3D graphics—remains challenging for developers, especially those with limited math skills. Mastering matrices is vital for leveraging Maya's full potential. This article focuses on practical matrix applications in Maya, avoiding complex mathematical details. It's beneficial for readers with basic Python skills, familiarity with object-oriented programming, and experience with Maya's Python API.

What is a transformation matrix

A transformation matrix is used to represent an object's position, rotation, and scale in space because it provides a compact and efficient way of encoding these transformations into a single unit - matrix. When we multiply matrices, we combine transformations into a final matrix. The order of multiplication is crucial factor in determining the resulting transformation. Just imagine, if you rotate an object several times around a random axis in Maya viewport and then repeat the same rotations but in a different order will produce different results. At the end, we apply the resulting matrix to an object (it can also be a point or a vector).

If you are a Maya user, you use matrices in every project. A good example of a matrix is a transformation node (or a group in the Maya Outliner). Groups are a convenient way to organize a Maya scene. At the same time, they serve as a hierarchy of transformations. Every group has its own transformation matrix. When you create a hierarchy of groups and place a Cube under it, the final position and rotation of the Cube is the result of multiplying the matrices of all groups, including the Cube's own matrix.

...
Cube position is a result of group3.matrix * group2.matrix * group1.matrix * cubeTransform.matrix

A transformation matrix is composed of 16 floating-point numbers. Each number represents a different aspect of the transformation being applied. There are a lot of articles explaining what each number means and how to get/set, for example, rotations values from the matrix. But, if we know how to use Maya Python API (or any other API), we can easily extract values (rotation, position, scale etc.) from the matrix with just one command.

Lets check how we can retrieve an object's world matrix using Maya Python API and extract object's coordinates from it:

# Import Maya commands and OpenMaya API
import maya.cmds as cmds
import maya.api.OpenMaya as OpenMaya

# Create a selection list and add an object to it
selection_list = OpenMaya.MSelectionList()
selection_list.add("pCube1")  # Add 'pCube1' to the selection list

# Retrieve the DAG path of the first object in the selection list
dagPath_Cube = selection_list.getDagPath(0)

# Get the world matrix of the object
# This matrix is not a transformation matrix yet, instead it's more a mathematical entity
world_matrix = dagPath_Cube.inclusiveMatrix()

# Extract translation values from the matrix
# For that we convert a matrix into a transformation matrix 
# in order to get access to a set of useful commands (for example "translation")
transformation_matrix = OpenMaya.MTransformationMatrix(world_matrix)
translation = transformation_matrix.translation(OpenMaya.MSpace.kWorld)

# Print the coordinates values
print(translation)  # Output: (-0.875594, 1.21776, 0.309122) - this is where our object is located in Maya scene

An Identity matrix is an empty matrix that represents zero transformations. We typically do not apply it to an object (If we apply it to an object - that object won't change its position at all), but it can serve as a starting point where we can set, for example, only rotation or translation values. For instance, if we have specific coordinates and wish to move our object to that position while preserving its rotation and scale, we create an Identity matrix, set the translation (now our identity matrix becomes a translation matrix), and multiply the object matrix by the translation matrix.

If we want to reverse the transformation of our objects, we can take the matrix we used to transform our object and invert it. Then, we multiply our object's matrix by the inverted matrix. Inverse matrices are also helpful for converting between different coordinate spaces in Maya, which we will discuss later.

...

Matrices can be modified and multiplied in various ways to achieve a range of effects. For instance, we can rotate an object around a particular point or axis in space, and position it on the surface of another object.

Object, Local and World Spaces

In Maya, World, Local, and Object spaces are three different coordinate systems that make it possible to visualize and manipulate objects in the Viewport.

...

World and Local spaces are used to represent the position, orientation, and scale of an individual object. We can say that a transformation matrix is a coordinate system (and vice versa). When we apply a matrix to an object, we modify object's position and orientation, but it doesn't actually move the object's vertices. Instead, it modifies a coordinate system of the object and Maya Viewport just redraws it in its new location. Magic!

World space is the global position of an object in the Maya scene, where the origin is located at the center of the grid. The world matrix represents the global position of the object.

Local space is a bit more complex concept in Maya. Every object in the Viewport can be selected and manipulated individually thanks to its parent - a transform node. This node provides the object with its own coordinate system, known as the Local space. Moving the object means moving its coordinate system, while the mesh vertices remains unchanged in their space. If the object has no parent, its Local and World spaces are equivalent. Objects can be placed within other groups (transform nodes) in Maya, creating a hierarchy of transformations where a child group inherits its parent's transformations while also having its own transformations. This hierarchical structure is what makes it possible to manipulate objects in Maya Viewport in a complex way (think about any rigged character).

Object space is a coordinate system that exists within a polygonal mesh. Each vertex of the mesh resides in the Object space. If the mesh (its transform node) is moved, its vertices' coordinates remain unchanged, as the Object space follows the Local space. This means that, from Maya perspective, the vertices stay in the same positions. However, if an individual vertex is moved, it will have a new coordinate, and the mesh is considered as deformed. The Object space origin is initially located at the center of the mesh.

Pivot is a key aspect in object transformations in Maya. Every object in the Viewport has its own Pivot, which serves as a reference point for operations like moving, rotating or scaling. When we perform a transformation on an object, it takes place around the object's pivot.

With Maya Python API we can easily retrieve an object's vertex position in world space coordinates (full code is here):

world_matrix = get_world_matrix(object_name)
vertex_iterator = OpenMaya.MItMeshVertex(object_name)

while not vertex_iterator.isDone():
    local_position = vertex_iterator.position(OpenMaya.MSpace.kObject)  # Object Space position
    world_position = local_position * world_matrix  # Convert to World Space position
    print(f"Object space: {local_position}, World space: {world_position}")
    vertex_iterator.next()

What about vectors?

Computer graphics heavily rely on vector math. Vectors serve the fundamental purpose of representing direction. A transformation matrix, being a 3D coordinate system, stores information about rotation. It is possible to obtain the individual axes from the matrix. Conversely, if the directions of all three axes (represented by three perpendicular vectors) are known, a transformation matrix can be constructed from them.

As an illustration, to find the Z-axis world direction of an object as an MVector, we multiply a vector (0, 0, 1) by the object's world matrix.

import maya.api.OpenMaya as OpenMaya
  
world_matrix = dag_path.inclusiveMatrix() # get world matrix from an object's DagPath
z_axis = OpenMaya.MVector(0,0,1) # define which axis we want to find in World Space

axis_vector = z_axis * world_matrix

To generate a transformation matrix (specifically a rotation matrix) from three known vectors, these vectors must be mutually perpendicular. If the vectors are not strictly perpendicular, the resulting object will have skewed geometry. Assuming we have three mutually perpendicular vectors aim, up and normal, representing X,Y,Z axes respectively, the following code can be used to build a transformation matrix from these vectors:

import maya.api.OpenMaya as OpenMaya
# ...
# we have 3 vectors - aim, up and normal, all in world space and mutually perpendicular
matrix = OpenMaya.MMatrix((
    (aim.x, aim.y, aim.z, 0),
    (up.x, up.y, up.z, 0),
    (normal.x, normal.y, normal.z, 0),
    (0, 0, 0, 1.0)
))

transformation_matrix = om.MTransformationMatrix(matrix)

You might be wondering why it's necessary to build a transformation matrix from these specific vectors. Imagine you need to position an object on another object's face, not just moving it but also rotating it appropriately, similar to how a scattering tool places stones on a hill's landscape. For this, you would select a random (or based on a Perlin noise mask) face's center on the landscape mesh, determine the direction of the face's normal (up vector), locate the center of its edge (aim vector), and calculate the cross product of these two vectors (normal vector). Constructing a transformation matrix from these three vectors enables precise placement and rotation of the object on the face's surface.

...

I like this example because it functions similarly to the Rivet constraint in Maya. The concept involves positioning an object onto a face of another object and orienting it in such a way that it aligns with the face's direction. The key aspect of this example is the need to construct a transformation matrix entirely from scratch, using three vectors as inputs. These vectors represent the coordinate system axes of the transformation matrix, and we must read the face data to obtain them. Feel free to use and modify this script as part of a larger scattering tool.

Maya and Matrices

In Maya, there are numerous methods to manipulate matrices. Although a certain degree of programming and mathematical proficiency is required to work with matrices directly, Maya offers various options for programmers to utilize matrices in tools for artists.

One of the ways to play around with matrices is to use Node Editor. Maya Node Editor provides a fast method for prototyping logic. However, its nodes can be non-intuitive, requiring time to find the correct nodes for tasks such as creating a point or multiplying it by a vector or a matrix. Once you become familiar with nodes necessary for matrix and vector mathematics, you will find it straightforward to prototype your logic. All Transform nodes in Maya provide us with all necessary matrix data (Matrix, Parent Matrix, World Matrix, Inverse Matrix, Translate Rotate Scale etc.) that we can connect to other nodes to see the immediate effect.

In addition to nodes, Maya offers API to perform matrix and vector operations and simplify object transformations. You can use the maya.cmds module or access the Maya API via C++ or Python. In my experience, the best way to work with transformations is using Maya Python API (OpenMaya), which offers a vast array of commands and classes, making it both powerful and easy to use, without having to navigate the complexities of C++.

OpenMaya provides several ways to manipulate matrices. Depending on the use case, we may work with matrices differently; for instance, we use a specific approach for simple commands versus deformer or node plugins. However, in most cases, when we want to run a script once to achieve a particular effect, we can use several OpenMaya classes to work with matrices and transformations.

Let's explore some classes that are frequently used for working with matrices and transformations.

  • MPoint represents a 3D point in space
  • MVector represents a vector in 3D space
  • MSelectionList is a container class that allows us to store objects. We can add objects to the Selection List by name and request object's DagPath
  • MDagPath represents a path to a particular object in the DAG (Directed Acyclic Graph) hierarchy of Maya (objects that we see in Maya scene). Many OpenMaya classes work with MDagPaths instead of objects names.
  • MMatrix is a low-level representation of matrices used for calculations.
  • MTransformationMatrix is a high-level representation of matrices that offers a useful set of methods to get or set rotation, translation, scale, and pivot values. It can be easily converted to an MMatrix (and vice versa).
  • MFnTransform is a class that provides access to transform nodes in Maya. It allows us to directly modify the transformations of objects. MFnTransform only works with MTransformationMatrix.

For example, here's how to obtain and update object matrices:

import maya.api.OpenMaya as OpenMaya

selection_list = OpenMaya.MSelectionList()
selection_list.add("pCube1")
dag_path = selection_list.getDagPath(0) 
world_matrix = dag_path.inclusiveMatrix()

# ... some calculations with world_matrix

# convert world_matrix into MTransformationMatrix
transform_matrix = OpenMaya.MTransformationMatrix(world_matrix)
transform_fn = OpenMaya.MFnTransform(dag_path) # get access to Transform node of pCube1
transform_fn.setTransformation(transform_matrix) # apply the transformation matrix to pCube1 transform node

What if we want to align objects in terms of position and orientation, meaning moving one object to the coordinates of another and rotating them to match axes? To achieve this, we can apply the world matrix of one object to another. Ultimately, both objects will share the same position, rotation, and scale.

import maya.api.OpenMaya as OpenMaya

# find Cube world matrix
world_matrix = dag_path_cube.inclusiveMatrix() # get World Matrix of the 1st object. Returns MMatrix
transformation_matrix = OpenMaya.MTransformationMatrix(world_matrix) # convert MMatrix into MTransformationMatrix

# set this matrix to Cone
fn_transform = OpenMaya.MFnTransform(dag_path_cone) # wrap the 2nd object with MFnTransform class
fn_transform.setTransformation(world_matrix) # MFnTransform class allows us apply transformation matrix to an object

Here are a few more examples of how you can work with matrices in Maya:

  • Move an object along a local axis of another object (code)
  • Rotate an object around a local axis of another object (code)
  • Rotate an object around a random vector (code)
  • Move an object up and undo it (code)
  • Get world coordinates of an object's vertices (code)
  • Extract an axis vector from an object's world matrix (code)

Conclusion

Why did I write this article? Firstly, recalling the extensive time I spent learning matrices the hard way, I now aim to share my knowledge in a simplified, user-friendly manner. Secondly, I intend to use this article and its code samples as personal references. And thirdly, why not? :D I hope this has provided new insights or, if you spot a mistake, you know where to find me.