January 4 – Quaternion Fun Times
Background:
I’m building a character customization screen, where a user can mix and match different character parts, like arms, legs, etc. in order to create a custom, character.
Additionally, the entire character will be rotating at a set speed while the user chooses parts.
To properly connect new parts, we need to follow the following steps:
1. Connect the arm shoulder joint position to the torso shoulder joint position
2. Rotate the new arm to match the current body rotation
3. Rotate the new arm to match it’s elbow joint to the torso elbow joint
If we perform the first two steps, we get this:

The second set of arms is set correctly to the torso shoulder joints, but its elbow joints aren’t properly lined up.
The current code is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 |
public void ConnectJoint (GameObject part) { // PartInformation stores shoulder and elbow joint position in each individual arm PartInformation info = part.GetComponent<PartInformation> (); // Set arms to current torso rotation part.transform.rotation = torso.rotation; // Set arms shoulder joint to torso shoulder joint position info.shoulderJoint.transform.position = torso.leftUpperShoulderJoint.transform.position; } |
We need to be careful here. The transform structure of the arm is:
If we move the “left_upper_shoulder_joints” transform, we create an offset from it’s parent. We could use local rotation of the joint to work around this issue, but let’s instead keep everything together. We’ll figure the offset of the parent and child, then account for that when moving the parent:
1 2 3 4 5 6 7 8 9 10 11 12 |
public void ConnectJoint (GameObject part) { // PartInformation stores shoulder and elbow joint position in each individual arm PartInformation info = part.GetComponent<PartInformation> (); // Set arms to current torso rotation part.transform.rotation = torso.rotation; // Set arms shoulder joint to torso shoulder joint position Vector3 jointDiff = info.joint.position - part.transform.position; part.transform.position = torso.leftUpperShoulderJoint.transform.position - jointDiff; } |
Next we need to rotate each set of arms to match the direction of the torso.
First, we get both the rotation between the torso’s shoulder and elbow joints, and the rotation between the arm’s shoulder and elbow joints, then plug them into this nice little Unity function:
Our fromDirection is the current arm direction, and our toDirection is the current torso arm direction:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public void ConnectJoint (GameObject part) { // PartInformation stores shoulder and elbow joint position in each individual arm PartInformation info = part.GetComponent<PartInformation> (); // Set arms to current torso rotation part.transform.rotation = torso.rotation; // Set arms shoulder joint to torso shoulder joint position Vector3 jointDiff = info.joint.position - part.transform.position; part.transform.position = torso.leftUpperShoulderJoint.transform.position - jointDiff; Vector3 fromDir = info.elbowJoint.transform.position - info.shoulderJoint.transform.position; Vector3 toDir = torso.leftUpperArmElbow.transform.position - torso.leftUpperShoulder.transform.position; Quaternion rot = Quaternion.FromToRotation (fromDir, toDir); part.transform.rotation = rot; } |
Hmm, it appears that the rotation is off until it gets back to its original instantiation position. This tells us that our code isn’t accounting for the current rotation
In order to add two rotations together, we must actually multiply them. Let’s do a basic assignment operator for multiplication:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public void ConnectJoint (GameObject part) { // PartInformation stores shoulder and elbow joint position in each individual arm PartInformation info = part.GetComponent<PartInformation> (); // Set arms to current torso rotation part.transform.rotation = torso.rotation; // Set arms shoulder joint to torso shoulder joint position Vector3 jointDiff = info.joint.position - part.transform.position; part.transform.position = torso.leftUpperShoulderJoint.transform.position - jointDiff; Vector3 fromDir = info.elbowJoint.transform.position - info.shoulderJoint.transform.position; Vector3 toDir = torso.leftUpperArmElbow.transform.position - torso.leftUpperShoulder.transform.position; Quaternion rot = Quaternion.FromToRotation (fromDir, toDir); // part.transform.rotation = part.transform.rotation * rot; part.transform.rotation *= rot; } |
Weird, it seems like its rotating in the opposite direction.
While researching quaternions, I remember seeing something about quaternions being “non-commutative”, which simply means:
x*y != y*x
With this in mind, let’s change the last line and test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public void ConnectJoint (GameObject part) { // PartInformation stores shoulder and elbow joint position in each individual arm PartInformation info = part.GetComponent<PartInformation> (); // Set arms to current torso rotation part.transform.rotation = torso.rotation; // Set arms shoulder joint to torso shoulder joint position Vector3 jointDiff = info.joint.position - part.transform.position; part.transform.position = torso.leftUpperShoulderJoint.transform.position - jointDiff; Vector3 fromDir = info.elbowJoint.transform.position - info.shoulderJoint.transform.position; Vector3 toDir = torso.leftUpperArmElbow.transform.position - torso.leftUpperShoulder.transform.position; Quaternion rot = Quaternion.FromToRotation (fromDir, toDir); // Quaternions are non-commutative part.transform.rotation = rot * part.transform.rotation; } |
Woo! So, quaternion addition being non-commutative, this is correct:
1 |
part.transform.rotation = rot * part.transform.rotation; |
And this is not:
1 |
part.transform.rotation = part.transform.rotation * rot; |
To attempt to explain, adding two quaternions together (by multiplication) is synonymous to performing the first rotation, then the second, one after the other.
In other words, you’re taking the first quaternion rotation, and applying it to the second.
Since we want to rotate the arm itself, we need to apply our arm rotation to the current torso rotation.
The torso rotation (part.transform.rotation) is essentially a constant horizontal spin. If we apply the current torso rotation to our new arm rotation, we’re applying a horizontal spin to our new arm. Then applying the needed arm rotation on a different axis.
That’s how I understand it, but don’t quote me, I could be completely wrong!
Anyway, I hope this helps with quaternion troubles, and everyone can avoid the same hangups I had!