Rig Design

Introduction

A Rubik's Cube seems like it should be a simple rig. Each move is just a rotation of a group of cubes.

In practice, however, a Rubik's Cube exposes several limitations of traditional animation rigs. The challenge is not computing the rotations themselves, but designing a system that remains intuitive to animate, easy to edit, and faithful to the behavior of a physical cube.

This page walks through the design decisions that led to the final implementation.

The Naïve Solution: Animate the Cubelets

At first glance, rigging a Rubik's Cube seems trivial. Each move is simply a 90° rotation of a subset of cubelets about one of the cube's principal axes. The most straightforward solution is therefore to set the pivot point of each cubelet to the center of the cube and directly animate the rotation of each cubelet.

In practice, this immediately runs into one of two problems: Euler interpolation or Maya's limited access to the quaternion SLERP parameter.

Euler Interpolation

The most obvious implementation is to keyframe the rotation of each cubelet using Euler angles.

This works for a single isolated turn, but problems quickly appear once cubelets undergo rotations about multiple axes. Eventually the cubelet encounters gimbal lock. At this point the rotation axes are no longer independent, making the interpolation unpredictable and introducing discontinuities into the animation. Even before reaching a true gimbal lock configuration, Euler interpolation may choose unintuitive interpolation paths that do not correspond to the physical motion of a Rubik's Cube.

For a rig intended to support arbitrary sequences of moves, relying solely on Euler rotations of the cubelets is therefore not a solution.

Quaternion Interpolation

A natural alternative is quaternion interpolation.

Quaternions eliminate gimbal lock and always interpolate along the shortest rotational path. For the Rubik's Cube, where every move is an exact 90° rotation, this initially appears to be the ideal solution.

Unfortunately, Maya's quaternion spherical linear interpolation (SLERP) produces constant angular velocity between keyframes. While it is theoretically possible to feed an animation curve into the interpolation parameter in order to add ease-in and ease-out, Maya does not expose the SLERP method in a way that can be edited within the DAG.

With most rigs, an animator expects to shape motion using the cubic Bézier curves in the graph editor. With Maya’s quaternion interpolation, the rotation speed remains constant regardless of the graph editor curves, leaving little artistic control over the timing of the turn.

As a result, quaternions solve the mathematical problem of interpolation but introduce a usability problem for animation. The cube rotates in a physically correct manner, but not in a way that is aesthetically pleasing.

Dynamic Ownership

Every move changes which layer a cubelet belongs to.

Consider a cubelet on the upper-front-right corner of the cube. Before any moves, it belongs to the top row, the front slice, and the right column. After rotating the front face, that same cubelet still belongs to the front slice, but it now belongs to a different row and a different column. The controls responsible for driving that cubelet have changed.

In other words, the hierarchy is no longer static. The ownership of each cubelet changes continuously throughout the animation.

Maya's DAG is designed around fixed parent-child relationships. While objects can be reparented, changing the hierarchy while keyframing is not a practical solution, and changing the hierarchy during playback is not possible. Instead, this type of rig is typically implemented using parent constraints with animated space switching.

Layer Controls

The shortcomings of animating individual cubelets suggest a different approach. Instead of rotating the cubelets directly, the animator rotates controls representing the rows, columns, and slices of the cube.

This immediately solves the interpolation problems described in the previous section. Each control rotates about only a single axis, eliminating the possibility of gimbal lock while still allowing the animator to use standard Euler animation curves. Unlike quaternion interpolation, the motion can be shaped with familiar ease-in and ease-out tangents, giving the animator complete artistic control over the timing of each turn.

From an animation perspective, this is a much more intuitive workflow.

Unfortunately, it introduces a more fundamental problem.

Space Switching

A parent constraint allows a cubelet to switch between different layer controls while preserving its world-space transform.

However, each ownership change requires two pieces of animation data:

  1. The parent constraint weights must be keyed to switch from one control to another.

  2. The constraint's offset must also be updated so that the cubelet remains stationary during the transition.

These two operations are tightly coupled. A tool or scriptjob can certainly automate the creation of the required keys, making the initial setup manageable.

The difficulty comes later.

Suppose an animator decides to add or remove a turn near the beginning of the animation. Every subsequent ownership change is now incorrect. The parent switches no longer occur at the right times, and every stored offset was computed from a cube state that no longer exists. The entire remainder of the animation becomes stale and must be regenerated.

The rig has become fragile. Small edits early in the timeline require rebuilding large portions of the animation.

Baking the Animation

A possible solution is to avoid dynamic ownership entirely is by baking the animation directly into the cubelets.

Instead of maintaining layer controls throughout the animation, the controls are used only to generate the motion. The resulting transforms are then baked onto each individual cubelet, producing a conventional animation where every cubelet has its own animation curves.

This approach eliminates the need for animated parent switching and constraint offsets because the hierarchy is no longer changing. Once baked, each cubelet simply follows its own transform animation.

The drawback is that the rig has effectively been discarded.

Any changes to the animation require returning to the original controls, rebaking the affected section, and replacing the existing cubelet animation. As the animation becomes more complex, this iteration cycle becomes increasingly cumbersome. Simple edits that would normally involve adjusting a few control curves instead require regenerating large portions of the animation.

While baking is often an acceptable final export step, it is poorly suited to the iterative nature of animation production, where timing, spacing, and poses are continually refined throughout the shot.

The Rubik's Cube as a State Machine

The common thread between all of the previous approaches is that they attempt to represent a Rubik's Cube as a hierarchy of transformations.

A Rubik's Cube is not a hierarchy.

It is a state machine.

At any point in time, the cube has a well-defined state consisting of the position and orientation of every cubelet. A turn is simply a state transition. Once that transition has been committed, it becomes the starting point for every future move.

This distinction is subtle but fundamental.

Traditional Maya rigs are stateless. Given the current values of the controls, the output of the rig can always be determined. The dependency graph evaluates the current frame without needing to know anything about what happened previously.

A Rubik's Cube does not satisfy this assumption.

Instead, the cube behaves much more like a simulation than a conventional rig. The controls do not directly describe the final pose of the cube. Rather, they act as inputs to the state machine, specifying the current move being performed. In this sense, the controls are better thought of as simulation parameters than transform drivers.

This observation leads to a very different architecture. Rather than encoding every ownership change into constraints or baked animation, the rig simply stores the committed state internally. Then the controls describe the current move, while the rig reconstructs the dynamic ownership and cubelet transforms from the accumulated cube state.

Treating the Rubik's Cube as a state machine solves the ownership problem, but it introduces a new challenge. The state of the cube is no longer fully described by the animation timeline. In a conventional Maya rig, jumping to any frame is deterministic. The dependency graph evaluates the controls at that frame, and the resulting pose is uniquely defined. The timeline itself is the source of truth.

A state machine breaks this assumption.

The control’s keyframes evaluate consistently every frame, but the cube's committed state depends on the sequence of moves that occurred before reaching that frame. Two evaluations of the same frame can therefore produce different results depending on the path taken through the timeline.

One important distinction is that this limitation only applies to non-sequential evaluation.

During normal animation, the dependency graph evaluates the timeline sequentially. Whether the animator is playing the animation or stepping through it frame by frame, every completed turn updates the committed cube state before the next frame is evaluated. The state machine therefore behaves exactly as expected with normal animation, and the animation remains fully interactive.

The problem only arises when the evaluation order changes. Consider an animation where the cube begins in a solved state at frame 1. The top layer is rotated at frame 20, and the front layer is rotated at frame 40. If the animator jumps directly from frame 40 back to frame 1, the control values correctly return to their original positions. However, the cube's committed state still reflects the top layer then front layer rotation that occurred later in the animation, and playing the animation again from 1 to 40 will apply the keyframed layer turns to that new cube state. Simply evaluating the controls at frame 1 is no longer sufficient to reconstruct the original cube because the intermediate state transitions have been skipped.

The dependency graph has no concept of rewinding a state machine.

The challenge therefore becomes one of state management. Once the cube's state exists independently of the timeline, a mechanism is needed to establish the correct committed state whenever non-sequential evaluation occurs.

The Complete Solution

The final design combines three complementary ideas.

The first is the use of layer controls. By animating rows, columns, and slices instead of individual cubelets, the rig preserves a familiar animation workflow based on editable Euler animation curves. Each control rotates about a single axis, eliminating gimbal lock and allowing animators to use standard Graph Editor tools for timing and easing.

The second is treating the Rubik's Cube as a state machine. Rather than attempting to encode changing ownership relationships into Maya's hierarchy, a custom dependency node maintains the committed state of the cube internally. During evaluation, the node reconstructs the current ownership of the cubelets and combines the committed state with the live control rotations to produce the final transforms.

The final piece is explicit state management. Because the committed state exists independently of the timeline, a companion Save/Load tool is necessary for cube permutations to be stored and restored whenever non-sequential timeline evaluation occurs. During normal playback and frame-by-frame scrubbing, the state machine behaves naturally as each completed move becomes the starting point for the next. The Save/Load tool simply provides a way to re-establish the correct committed state after arbitrary timeline jumps.

Together, these three components produce a rig that remains intuitive for animators while accurately representing the mechanics of a physical Rubik's Cube. The controls behave like those of a conventional Maya rig, while the underlying implementation embraces the stateful nature of a Rubik’s Cube rather than attempting to force it into a static transform hierarchy.

Next
Next

Node Architecture