Assuming the following matrix multiplication convention, where
M is the matrix you want to decompose, and the
p variables represent points as column vectors with a
w coordinate of 1...
p_transformed = M * p_local
M = | m00 m01 m02 m03 | assumed to be decomposible into
| m10 m11 m12 m13 | M = T * R * S
| m20 m21 m22 m23 | such that that...
| 0 0 0 1 |
p_transformed = T * R * S * p_local
T = | 1 0 0 tx | Translation by (tx, ty, tz)
| 0 1 0 ty |
| 0 0 1 tz |
| 0 0 0 1 |
S = | sx 0 0 0 | Axis-aligned scale by (sx, sy, sz)
| 0 sy 0 0 |
| 0 0 sz 0 |
| 0 0 0 1 |
R = Rotation matrix (orthonormal)
Then it is possible to compute a choice of translation
R, and scale
S with the same combined result as
But there is no guarantee that these will be the same values originally used to create
M, as we'll see below.
You also may lose precision due to rounding errors along the way, so you should avoid repeated round-trip conversions between
S, such as when laying out objects in a level editor, saving and loading as you go.
This also goes for updating the transform of a dynamic object at runtime. Being able to rebuild the matrix from raw translation, rotation, and scale values will help you avoid harmful rounding errors.
So, it will almost certainly be worth keeping the extra memory to store your original transformation parameters, both for consistency from a user's perspective, and for accuracy/stability.
That said, if it's something you're using in a read-only manner, or you need to reconstruct transformation parameters from baked data which only contains combined matrices, you can do it like this...
First, extracting the translation component is direct & unambiguous:
tx = m03 ty = m13 tz = m23
The scale can be computed as the length of the basis vectors multiplied by
sx = || M * (1, 0, 0, 0) || (and similarly for sy, sz)
= || (m00, m10, m20) ||
= sqrt(m00 * m00 + m10 * m10 + m20 * m20)
If your scales can be negative then you have an added complication to solve. A reflection in any one axis is identical to a reflection in a different axis combined with a suitable rotation, and a reflection in two axes is equivalent to a pure rotation.
So, you'll need to pick a convention for resolving this. One is to detect whether there is a net reflection (ie. a reflection along 1 or 3 axes) and decide by fiat that it should be a reflection along the x axis:
cross((m00, m10, m20), (m01, m11, m21)),
(m02, m12, m22)
) < 0)
sx *= -1
sx or any other scale is zero then you're dealing with a planar, line, or point projection, so we can express it as a rotation without reflection)
S in hand we can divide it out to reconstruct an appropriate
R = | m00/sx m01/sy m03/sz 0 |
| m10/sx m11/sy m13/sz 0 |
| m20/sx m21/sy m23/sz 0 |
| 0 0 0 1 |
Note that if one of
sz is zero, you'll want to replace the corresponding column with the cross product of the other two to avoid a divide by zero error.
If two axes have zero scale, you can pick two arbitrary unit vectors mutually orthogonal with the remaining axis. If all three axes have zero scale, you can let
R = Identity since a point is a point regardless of rotation.
R should be an orthonormal rotation matrix, but in practice numerical errors can cause it to deviate, so including an error-correction step where you orthonormalize it is probably a sensible precaution.
You can use the usual methods to convert that rotation matrix into an equivalent unit quaternion or Euler angle triplet if needed, so I won't elaborate on that here.