Node Architecture
Introduction
This page describes how the dependency node constructs the state of the Rubik's Cube and computes the live cubelet matrices during evaluation.
Rather than discussing individual methods, the focus is on the data required by the node and how that data flows through the evaluation process.
The node maintains two categories of state:
Persistent state, which is serialized with the Maya scene and survives scene save/load.
Transient state, which exists only during evaluation and is reconstructed whenever necessary.
Within the persistent state, it is useful to distinguish between the cube state and the control state.
The cube state consists of the serialized cubelet matrices together with cached control rotations. It represents the committed state of the Rubik's Cube and is written back to the node at the end of every compute evaluation.
The control state consists of the cached control rotations alone. Unlike the cube state, the control state is only updated when an individual control reaches a completed quarter turn. These cached rotations therefore represent the orientation of each control at the last committed control turn rather than at the last compute evaluation.
This distinction becomes important throughout the evaluation process. The cube state describes the committed arrangement of the puzzle, while the control state provides the reference rotations used when reconstructing cubelet offsets and transferring ownership between control families.
The following sections describe how this persistent state is combined with the transient state to construct the live cubelet transforms during evaluation.
Computing Cubelet Transforms
Every cubelet is ultimately evaluated in the same way.
The node determines two pieces of information:
The cubelet's offset relative to its controlling layer.
The current transform of that layer control.
The final cubelet matrix is then obtained by applying the control transform to the stored offset.
[Cubelet Matrix] = [Control Matrix] x [Cubelet Offset Matrix]
The control matrix is constructed from the current live control rotations supplied by the animator.
The cubelet offset is reconstructed from the node's cached state. Computing this offset requires knowledge of the cubelet's membership, the current ownership of the cube, the appropriate set of control rotations, and the cubelet's committed transform.
The remainder of the node is therefore concerned with constructing and maintaining this data so that the offset can be reconstructed whenever the cube state changes.
Live Control Matrices
The simpler of the two quantities is the control matrix.
Each row, column, and slice control provides a single rotation value as an input to the node. During evaluation, this rotation is converted into a transformation matrix representing the current orientation of that control. The node assumes:
Row controls rotate about the Y axis.
Column controls rotate about the X axis.
Slice controls rotate about the Z axis.
These matrices represent the animator's current input. Unlike the cubelet offsets, they are not reconstructed from the node's cached state. Instead, they are built directly from the live control rotations each time the node evaluates.
Because each control rotates about a single axis, the entire control matrix can be constructed from a single rotation value. No additional matrices or transform channels are required.
Cubelet Offsets
The offset represents the cubelet transform expressed in the local space of its controlling layer. It is computed by removing the control transform from the cubelet transform:
[Cubelet Offset Matrix] = [Control Inverse Matrix] x [Cubelet Matrix]
where the control matrix is chosen according to the cubelet's current owner. Once constructed, the offset remains constant while that control family owns the cube.
The challenge is determining which control matrix should be used to construct the offset. Because ownership changes throughout the animation, the node must reconstruct the offset whenever a new control family acquires ownership or whenever the transient state is rebuilt.
Constructing the offset therefore requires four pieces of information:
Membership – Which row, column, and slice each cubelet currently belongs to.
Ownership – Which control family currently owns the cube and, therefore, which individual control drives each cubelet.
Control Rotations – Either the control rotations from the last committed control state or the initial control rotations from the previous compute evaluation, depending on the stage of evaluation.
Cubelet Matrix – The transient cubelet matrix.
The transient state maintained by the node exists primarily to organize this information for efficiently calculating the offset
Membership and Ownership
Membership
Membership answers a purely geometric question:
Which row, column, and slice does each cubelet currently belong to?
The node determines this directly from the cubelet positions stored in the transient cubelet matrices. Because the cubelets are arranged on a regular grid, each cubelet's position uniquely determines its membership.
For odd-sized cubes, the valid layer coordinates are on integer values centered about the origin:
coordinate = i - (N - 1) / 2
Even-sized cubes do not contain a center layer, so the coordinates lie halfway between integer values:
coordinate = (i + 0.5) - N / 2
where:
N is the cube size
i is the control index, beginning at 0 and ending at N - 1
Every cubelet coordinate maps directly to the corresponding control index, with control[0] representing the most negative coordinate along an axis and subsequent controls progressing toward the positive direction.
For example, in a 3×3×3 cube, a cubelet located at (-1, 0, 1) is a member of:
column[0]
row[1]
slice[2]
When all layers of a cube are at a completed quarter turn, every cubelet has exactly one row membership, one column membership, and one slice membership.
Ownership
Ownership answers a different question:
Of the three possible memberships, which one currently drives the cubelet?
Only one control family may own the cube at a time. The current owner is stored by the activeFamily attribute, an enum with the values:
None
Row
Column
Slice
When the active family is Row, each cubelet is driven by its row membership. Likewise, when the active family is Column or Slice, the corresponding membership determines the control that owns the cubelet.
Ownership determines which control is used to calculate the cubelet's offset and, ultimately, its output matrix.
When activeFamily is None, no control family currently owns the cube. This state occurs immediately after every control in the previously active family reaches a completed quarter turn.
During this stage, the node rebuilds the cubelet memberships from their new positions before determining whether another control family has already begun rotating. If so, ownership is immediately transferred using the newly calculated memberships. Otherwise, the cube remains without an owner until a new move begins.
Live, Initial, and Cached Control Rotations
The node maintains three sets of control rotations: live, initial, and cached. Although all three describe the orientation of the layer controls, each represents the controls at a different point in time and serves a different purpose during evaluation.
Live Control Rotations
The live control rotations are read directly from the connected animation controls each time the node evaluates. These rotations represent the animator's current input and are used to construct the live control matrices that drive the cubelets.
At the end of every compute, the live control rotations are written to the node's persistent cache as the initial control rotations. These stored values do not affect normal evaluation. Instead, they preserve the control state from the end of the previous compute so it can be recovered if the transient cache must later be reconstructed.
Initial Control Rotations
The initial control rotations are the live control rotations from the previous compute. They are only used when the node reconstructs its transient cache.
The transient cache becomes invalid whenever the node cannot rely on previously computed data, such as when a scene is first opened or a cube state is loaded from the Save/Load tool. In these situations, the node must reconstruct its transient data from the serialized state stored in the node's persistent cache.
This becomes particularly important if the scene is saved while a move is already in progress.
Consider a cube that is halfway through a turn when the scene is saved. When the scene is reopened, the committed control state still represents the last completed move, while the live controls and cubelets are positioned partway through the next move.
The transient cache is not reconstructed immediately when the scene is loaded. Instead, it is rebuilt during the node's first compute, which is typically triggered by one of the controls being rotated. By the time this compute begins, the live control rotations already reflect the change that triggered the evaluation rather than the control state that existed when the scene was loaded.
For this reason, the node cannot rely on the live control rotations to reconstruct its transient cache. Instead, it uses the stored initial control rotations, which represent the live control rotations from the previous compute before the scene was saved. These rotations allow the node to reconstruct the cubelet offsets exactly as they existed when the scene was loaded. Once the transient cache has been rebuilt, subsequent evaluations use the live and cached control rotations as normal.
Cached Control Rotations
Whenever a control reaches a completed quarter turn, its current rotation is stored as a cached control rotation. These cached values represent the orientation of each control at the last committed cube state.
Cached rotations become important whenever ownership transfers between control families.
Consider a case where the Row family is active while one of the Column controls has already begun rotating in preparation for the next move. As soon as the row move completes, ownership immediately transfers to the Column family. At this point, the cubelet offsets must be reconstructed using the cached column rotations rather than their current live rotations.
If the live rotations were used instead, each cubelet's offset would be calculated relative to a control that had already begun its next move. The completed quarter turn would no longer correspond to an exact 90° relationship between the cubelet and its controlling layer. As a result, the cubelets would no longer align with the controls at completed turns, making it increasingly difficult for the animator to reach the exact quarter-turn positions required to commit a move.
By reconstructing the offsets from the cached control rotations, the node establishes the new ownership relationships at the last committed cube state. The live control rotations are then applied on top of those offsets, ensuring that every completed move remains aligned to exact 90° increments while still allowing the next move to begin immediately.
Compute Evaluation
The purpose of the compute method is to combine the node's persistent state with its transient state to produce the live cubelet transforms.
The persistent state contains the committed cube state and control state that survive scene save/load. The transient state contains the working data used during evaluation, including membership, ownership, cubelet offsets, and the transient cubelet matrices.
Most evaluations simply update the cubelet matrices using the existing ownership and offset information. Additional work is only required when the transient state must be reconstructed or ownership transfers between control families.
The overall evaluation can be summarized as:
Rebuild transient cache if necessary
↓
Handle cube size change if necessary
↓
Acquire ownership if necessary
↓
Evaluate cubelet matrices
↓
Update control state
↓
If ownership changes
Rebuild membership
Acquire new ownership
Re-evaluate cubelet matrices
↓
Commit cube state
1. Rebuild the Transient Cache
The compute begins by checking whether the transient cache is valid.
The transient cache is invalid whenever it cannot be recovered from the previous evaluation, such as when the node is first created, a scene is loaded, or a cube state is restored using the Save/Load tool.
When this occurs, the transient state is reconstructed entirely from the persistent state.
This reconstruction restores:
Membership
Ownership
Cubelet offsets
The transient cubelet matrices
The cube size
using the committed cube state together with the stored initial control rotations.
2. Handle Cube Size Changes
The node then verifies that the persistent cube state matches the transient cube size.
If the size has changed, the previous cube state is discarded and replaced with a solved cube of the requested dimensions.
The corresponding transient state is then rebuilt so evaluation can continue from a valid starting configuration.
3. Acquire Ownership
If a control family already owns the cube, evaluation continues using the existing ownership.
Otherwise, the node examines the committed control state and compares it to the live control rotations to determine whether one of the control families has begun a new move.
If no controls are rotating, evaluation is complete.
If a family has begun rotating, ownership is assigned to that family and the cubelet offsets are reconstructed.
This establishes a new reference frame before any live control rotations are applied.
4. Evaluate the Cubelets
With ownership established, every cubelet is evaluated in the same way.
For each cubelet,
[Cubelet Matrix] = [Live Control Matrix] x [Cubelet Offset]
The live control matrix comes directly from the animator's current input, while the cubelet offset comes from the transient cache.
The result is written to the transient cubelet matrices.
At this point, the persistent cube state has not yet been modified.
5. Update the Control State
After the cubelets have been evaluated, the node determines whether any controls have reached completed quarter turns.
If at least one control is still turning, ownership remains unchanged and the node can commit the cube state and end the compute.
6. Transfer Ownership
If every control in the active family has completed its turn, ownership is temporarily released.
At this point, the transient cubelet matrices represent a new cube configuration, but not necessarily the final cube configuration.
Using the newly computed transient cubelet matrices, the node:
Rebuilds cubelet membership.
From the control state, determine whether another control family has already begun rotating.
Assigns ownership to that family, if one exists.
Reconstructs cubelet offsets using the transient cubelets and new ownership.
Re-evaluates the cubelets using the new offsets and live rotations of the new control family.
This second evaluation allows ownership to transfer while maintaining the synchronization between cubelets and live controls.
7. Commit the Persistent State
Finally, the node updates its persistent state and ends the compute.
The following values are written back to the node:
The cubelet matrices stored in the outOffsetParentMatrix array
The committed control state stored in the cached[Layer]Rotation arrays
The active control family stored in the activeFamily enum
The current control rotations stored in the initial[Layer]Rotation arrays