I have a calculated matrix, and I need to instantiate a new object with the exact same transformation as the matrix describes.
Do I really have to extract position, rotation, and scale values from the matrix, or there is a nice and simple way to assign the whole matrix to Transform, which I haven’t found yet?
var position = new Vector3(11, 22, 33);
var rotation = Quaternion.Euler(10, 20, 30);
var scale = new Vector3(1, 2, 3);
Debug.Log("Before:");
Debug.Log(position);
Debug.Log(rotation);
Debug.Log(scale);
var m = Matrix4x4.TRS(position, rotation, scale);
position = m.ExtractPosition();
rotation = m.ExtractRotation();
scale = m.ExtractScale();
Debug.Log("After:");
Debug.Log(position);
Debug.Log(rotation);
Debug.Log(scale);
Just for future viewers. I had some problems with the above method to get rotation (but thanks for sharing it alex, it got me on the right track), something to do with division by zero caused by 180 degree rotations or something like that. so I got this off of Martin John Bakers maths pages EuclideanSpace.com. I havent tested it much yet but so far more luck with it.
public static Quaternion MatrixToQuaternion(Matrix4x4 m)
{
float tr = m.m00 + m.m11 + m.m22;
float w, x, y, z;
if(tr>0f){
float s = Mathf.Sqrt(1f + tr) * 2f;
w = 0.25fs;
x = (m.m21-m.m12)/s;
y = (m.m02-m.m20)/s;
z = (m.m10-m.m01)/s;
} else if( (m.m00 > m.m11) && (m.m00 > m.m22) ){
float s = Mathf.Sqrt(1f+m.m00-m.m11-m.m22) * 2f;
w = (m.m21-m.m12)/s;
x = 0.25fs;
y = (m.m01+m.m10)/s;
z = (m.m02+m.m20)/s;
} else if(m.m11 > m.m22){
float s = Mathf.Sqrt(1f+m.m11-m.m00-m.m22) * 2f;
w = (m.m02-m.m20)/s;
x = (m.m01+m.m10)/s;
y = 0.25fs;
z = (m.m12+m.m21)/s;
} else {
float s = Mathf.Sqrt(1f+m.m22-m.m00-m.m11) * 2f;
w = (m.m10-m.m01)/s;
x = (m.m02+m.m20)/s;
y = (m.m12+m.m21)/s;
z = 0.25fs;
}
Quaternion quat = new Quaternion(x, y, z, w); //Debug.Log("Quat is " + quat.ToString() );
return quat;
}
Very cool!
I’m a little curious though: if the matrix has both scale & rotation, won’t the rotation mess up the scale? or does the 4th component compensate for it somehow?
Also, your version uses the matrix row-by-row, and all 4 components, whereas alexzzzz does it column-by-column, using only the first 3 components:
Yeah I figured that, I noticed you don’t new any struct, only assign all their fields, and use ref as much as possible, very clean!
But one implementation or the other, they do exactly the same thing !
That doesn’t really tell me what kind of black magic makes it correct.
@numberkruncher , please correctly me if I’m wrong but It doesn’t look like your code would handle a negative scale. Any ideas on how to handle a negative scale?
I agree that mathematically it might not make sense, but in practice it’s very useful for flipping (especially in 2D)… I too would love to find a workaround for negative scale.
Here is a snippet from a website which may (or may not) be relevant to detecting the negative scale:
// The scale is simply the removal of the rotation from the non-translated matrix
Matrix4x4<T> scaleMatrix = Matrix4x4<T>::Inverse(rotation) * mCopy;
scale = Vector3D<T>(scaleMatrix.rows[0][0],
scaleMatrix.rows[1][1],
scaleMatrix.rows[2][2]);
// Calculate the normalized rotation matrix and take its determinant to determine whether
// it had a negative scale or not...
Vector3D<T> row1(mCopy.rows[0][0], mCopy.rows[0][1], mCopy.rows[0][2]);
Vector3D<T> row2(mCopy.rows[1][0], mCopy.rows[1][1], mCopy.rows[1][2]);
Vector3D<T> row3(mCopy.rows[2][0], mCopy.rows[2][1], mCopy.rows[2][2]);
row1.Normalize();
row2.Normalize();
row3.Normalize();
Matrix3x3<T> nRotation(row1, row2, row3);
// Special consideration: if there's a single negative scale
// (all other combinations of negative scales will
// be part of the rotation matrix), the determinant of the
// normalized rotation matrix will be < 0.
// If this is the case we apply an arbitrary negative to one
// of the component of the scale.
T determinant = nRotation.Determinant();
if (determinant < 0.0) {
scale.SetX(scale.GetX() * -1.0);
}
A word of warning: the methods here will break down if the 3x3 submatrix of your 4x4 contains skew (which can be explained in a hand-wavy fashion as “non-uniform non-axis-aligned scaling”). Imagine a matrix having scale of (4, 1, 1), with the “4” scale being along some diagonal direction. Apply this to a cube and it will become slanted. That’s skew.
A fully-general decomposition of a 3x3 matrix is Rb * D * Ra. Where Ra are rotations and D is axis-aligned scale (ie, a diagonal matrix or a Vec3 of scale values). You can think of Ra as rotating the object into the orientation along which the axis-aligned scale values can be applied. Rb then rotates the object into its final orientation.
Performing this decomposition involves a bunch of deep math and if you need it, you should grab the code from David Eberly or some other authoritative source. But hopefully you just have axis-aligned scale, in which case Ra is identity.
Note that if you have non-uniform scale somewhere in your transform stack, it can cause skew; this is why transform has “lossyScale”, why you can’t just stuff a Mat4x4 into a Transform, etc etc.
Guys, what about changing the world matrix just before it will go into the shader? Does Unity has any inception point to make it happen? What I basically want is a billboard shader, where you can controll the sale and the position of the object in the editor. Any chance to do that?