World Space Getters
So far, we've only represented a transform in world space as a matrix. How can we retrieve the world space position / rotation / scale of a transform? We can retrieve world position and rotatin, we can't retrieve world scale. Unity calls this global scale, lossyScale.
Getting Global Rotation
To get the world rotation of a transform, recursivley multiply the rotation of the transform with it's parents. The code below does this, it uses an iterator instead of recursion.
Quaternion GetGlobalRotation(Transform t) { Transform iterator = t.parent while (iterator != NULL) { rotation = iterator.rotation * t.rotation; iterator = iterator.parent } return rotation; }
Getting Global Position
The easy way to get the world position of a transform would be to take the last row of the world matrix of the transform. However, we can save a few multiplications by calculating this value without any matrix operations. We need to take the position of the transform and apply it's parents transform to it in the same order as multiplying matrices would:
- scale first
- rotate next
- translate last:
Vector GetGlobalPosition(Transform t) { Vector worldPos = t.position; // Copy, not reference Transform iter = t.Parent while (iter != null) { // First apply parent scale worldPos = worldPos * iter.scale // Vec3 * Vec3 // Next apply parent rotation worldPos = worldPos * iter.rotation; // Quat * Vec3 // Finally apply parent translation worldPos += iter.position; // Vec3 + Vec3 iter = iter.parent } return worldPos; }
Getting Global (lossy) Scale
The rotation and scale of a parent transform affects the scale of it's child transforms, this can introduce skewing if a parent has a non-uniform scale. Because of this, retrieving the global scale of a transform is difficult. The first step in doing so is to find a 3x3 Matrix that holds both the rotation and scale of the transform. To find this matrix, convert the scale and rotation of the transform into matrices, combine them and recirsivley multiply with the parent transforms rotation and scale matrix.
Matrix3 GetGlobalRotationAndScale(Transform t) { Matrix3 scaleMat = Matrix3( t.scale.x, 0, 0, 0, t.scale.y, 0, 0, 0, t.scale.z ); Matrix3 rotationMat = ToMatrix(t.rotation); Matrix3 worldRS = rotationMat * scaleMat; // Recursivley concatenate with parent if (t.parent != NULL) { Matrix3 parentRS = GetGlobalRotationAndScale(t.parent); worldRS = parentRS * worldRS; } // Return scale rotation return worldRS }
Now that we know the global rotation and scale of the transform, we can remove the rotation component, leaving us with just the scale and skew matrix. To do this, find just the global rotation of the transform, invert that quaternion and turn it into a matrix. This new matrix is the inverse global rotation matrix of the transform. Multiply it with the scale and rotation matrix to remove the rotation component. The main diagonal of the resulting scale and skew matrix is the global lossy scale of the transform.
Vector3 GetGlobalLossyScale(Transform t) { // Find inverse global rotation (rotation only) of transform Quaternion rotation = GetGlobalRotation(t); Matrix3 invRotation = ToMatrix(Inverse(rotation)); // Find global rotation and scale of transform Matrix3 scaleAndRotation = GetGlobalRotationAndScale(t); // Remove global rotation from rotation & scale Matrix3 scaleAndSkew = invRotation * scaleAndRotation; // Mat3 * Mat3 // Return the main doagonal of the scale & skew matrix return Vector3(scaleAndSkew[0], scaleAndSkew[4], scaleAndSkew[8]); }
This method of retrieving global scale is covered in more detail in GPU Pro 5, Managing Transformations in Hierarchy by Bartosz Chodorowski and Wojciech Sterna