Skip to main content
GameDev.net gamedev.net
🔒 Locked 🎮 Unity

Adding or Concatenating Transforms (Scale, Rotation, Translation)

Started by Kwizatz Jan 8, 2013 at 11:18 PM 21 replies 15.5k views
Original Post
Kwizatz
Kwizatz

I keep my transformations as a scale vector, a rotation quaternion and a translation vector (SRT) rather than a single matrix, when I need a matrix I do some conversion and get a 4x4 matrix out of the 2 vectors and quaternion.

To add or concatenate 2 transforms what I do is generate the matrices from the 2 SRT transforms and multiply them together, which is fine since I rarely require to convert back from Matrix to SRT.

In the long run I think there are some matrix multiplications that can be shaved off if I could "add transforms", so I am trying to do that, but I ran into some issues.

My basic approach is as follows:

I have 2 SRT structures which I want to concatenate.

I do a element wise multiplication of the scale vector (S = S1[0] * S2[0],S1[1] * S2[1],S1[2] * S2[2]), this seems to work, but for now I have no scaling, so all values are 1, I just need to know if this is correct or if there is something wrong there.

I do a simple quaternion multiplication with R1 * R2, again this works as expected as the 3x3 sub-matrix is the same as when I convert to matrix and multiply matrices.

I to an element wise addition of the translation vectors so T = T1[0]+T2[0],T1[1]+T2[1],T1[2]+T2[2], and this is where it all goes wrong, the values on the matrices are different, and now that I think of it, this may have to do with the last element in the matrix, the 4th element of a position vector....

TL;DR version:

So anyway, long story short I want to concatenate/add transforms in Scale, Rotation, Translation format and then convert the result to a 4x4 matrix rather than convert the SRT's to matrices and then multiply the matrices, but the translation vector addition is giving me trouble.

Any ideas?

Thanks in advance!

Paradigm Shifter
Paradigm Shifter
You don't add translations when they are 4x4 homogeneous matrices, you (matrix) multiply them, that's the whole point of homogeneous matrices (all affine transforms can be multiplied). Just make sure the rest of the translation matrix is the identity (i.e. the upper 3x3 part).
"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley
Kwizatz
Kwizatz

Hey, thanks! that makes perfect sense, I though of something like that, but for some reason I kept thinking the 3x3 upper part had to already be populated with rotation and translation data instead of the identity... I am glad I posted. smile.png

Would scale work the way I am doing it though? I would like confirmation on that.

Thanks again!

Paradigm Shifter
Paradigm Shifter
Yeah, scale works the same way, just make sure the 4th row and column is (0 0 0 1). (So it's diagonal with zeros everywhere except leading diagonal which is (scalex scaley scalez 1).

EDIT: There is always a 1 in the bottom right corner of homogeneous transform matrices.
"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley
Kwizatz
Kwizatz

Hmmmm no, that didn't work, I get the same values.

With identity matrices for the SR part the T part becomes the sum I am already doing, but inverted (IE: T2 + T1 instead of T1 + T2).

The bottom right value does remain as 1, so no real change.

HappyCoder
HappyCoder
You will only be able to keep the pieces of the transformation separate if you only allow uniform scaling. This is because by appending transforms that don't enforce uniform scaling you can end up with a matrix that skews vertices. Being that you cannot skew vertices with a scale, rotation, then translation. You will not be able to concatenate transformations and keep them in the split representation.
My current game project Platform RPG
yckx
yckx

I don't think this is going to work. IIRC, if you have a transform matrix that has scaling and/or rotation info as well as translation, the scale and rotation are applied first. So the translation components can't be combined independently, unless the scale and rotation of both SRTs are identical. Matrix multiplication isn't commutative, so you can't really expect to get an accurate result if you change the order inl which you combine the component transforms.

Kwizatz
Kwizatz
I don't think this is going to work. IIRC, if you have a transform matrix that has scaling and/or rotation info as well as translation, the scale and rotation are applied first. So the translation components can't be combined independently, unless the scale and rotation of both SRTs are identical. Matrix multiplication isn't commutative, so you can't really expect to get an accurate result if you change the order inl which you combine the component transforms.

Yeah, you're right, I found out that if I apply the rotation of the first SRT to the translation of the second SRT before adding the 2 vectors, I do get the same vector as the matrix multiplications, give or take 0.000001 due to floating point error (scale is 1,1,1 so its mute at the moment, but I am sure I would have to take it into account as well).

My "rotate_vector_by_quaternion" packs quite a lot of operations, so now I am just pondering whether its worth it or not. In the end what I may be saving is a temp array of matrices since what OpenGL (and I suspect Direct3D) expects is matrices as well anyway.

Thanks for your help!

yckx
yckx

I wouldn't worry too much about trying to devise a more efficient way to concatenate transforms unless you've determined empirically through profiling that this bit of code is your bottleneck. Your first priority should be code that you can read and understand after not looking at it for six months. Multiplying matrices is a standard approach to transform concatenation, so let it be your first choice. Otherwise you run the risk of wondering what the hell you were trying to do, how it was supposed to work, and why you were doing it in the first place.

EduardoMoura
EduardoMoura

Yckx you just resumed my coding life!

I have been looking for a piece of code I wrote about 8 years ago, and I am still trying to figure out what the hell was I thinking when doing that.

Don't do the same mistakes, if you do change, comment through fully, you never know when you will need to revisit your old code. And believe me, sometimes when I am really sleepy or tired I code extraordinary things that work, do what is supposed, but I have no idea why.

So unless this really is your bottleneck (which I doubt), don't really bother trying to optimize the code.

Check out my new blog: Morphexe 
Kwizatz
Kwizatz

You're right, and I agree, my matrix code works, but to give some more context to this, I'll explain how this idea came to be.

My skeleton file format stores transforms as SRT, as I explained, my animation file format stores a "channel" per vector element, so animations are applied to the SRT vectors as well, this works great, BUT at runtime I need to generate a temp array of matrices (that is, an array of arrays) for the bind pose skeleton pose, a temp array of matrices for the animated joints which gets multiplied by the bind pose array and a final array for the result of the previous multiplication.

I cache the bind pose array since it never changes, but keeping duplicate data on memory kind of bothers me, and having a temp array with the SRT's converted to matrices provokes the same nagging annoyance in me.

So I though that if I could do the operations with the SRT's directly I could do without the auxiliary arrays, so no duplication of data in memory, and at the time avoid a runtime pre-computation (I am trying to do all preprocessing offline so my file formats are ready to use as soon as they're loaded into memory and some pointers are set), and some matrix multiplications here and there.

To me, it is an interesting topic, I have not dumped my matrix code yet, and I won't, but I'd like to know what could I get away with.

RobTheBloke
RobTheBloke

You can do everything with SRT (and upload them to the GPU and process the verts with them directly if you wish). Doing so has a number of problems. Firstly, SRT * Vertex is an exceptionally slow operation (well, it's not that bad, but it is much slower than matrix * Vertex). Secondly, SRT * SRT is also fairly slow when compared to matrix * matrix. Really the only time SRT makes any practical sense, is when you are performing lots of interpolations (which is quicker with SRT than with matrices). Most people store the bind pose as matrices because, well, it's efficient as is possible (assuming you have SIMD optimised your matrix operations. If you haven't, then you *may* be able to squeeze a tiny advantage with SRT).

Kwizatz
Kwizatz
In the end I did manage to get it working, I am doing this for animation interpolation, so it does seem justified. I convert to matrix before feeding the transforms to the gpu.
I do think this way is more efficient at least as long as it stays on the CPU, many multiplications can be avoided for example because one of the multiplicands being zero for the translation part and the scale part (what I did was convert the vectors to matrices and assume constants for the non variable elements in the matrix, then simplify the matrix multiplications),
the most expensive operation then becomes rotating the translation vector using the quaternion, since it implies a quaternion to 3x3 matrix conversion and a matrix vector multiplication but still not as many operations as the regular matrix multiplication.
You only need to apply rotation and scale to the translation, I think, because of the nature of translation, a skew in 4D, rather than being a linear transform in 3D, and in any case it makes sense when you think of the translation vector as being in the space of the rotated and scaled axes.

I am leaving SIMD viability as an exercise for later.
Dmytry
Dmytry

You could have a transformation that rotates by 45 degrees, then scales the x axis by a half, then rotates back. This transformation can not be represented with scaling vector, it happens along an axis that is at 45 degrees.

For the rotation and translation, your transform is like

V'=rotation*V+translation

and for two transforms, it is

V'=rotation2*(rotation1*V+translation1)+translation2

or

V'=rotation2*rotation1*V + rotation2 * translation1 + translation2

so you multiply your rotations and you add translations after multiplying the earlier translation by the later rotation.

The multiplication by rotation here means rotating by a quaternion, i.e. represents Q*a*~Q where ~ is inverse.

Kwizatz
Kwizatz

Hi Dmytry,

I am not sure I follow, what do you mean? in the end what my SRT operations do is a simplified matrix multiplication, so if you take S,R,and T as matrices with scale, rotation and translation respectively, and I as identity the operation you describe would be something along these lines:

( I * R * I ) * (S * I * I) * ( I * *R * I ),

which is the same as RSR, wouldn't the scale vector (diagonal) on the second matrix be doing the scale on world axes rather than object axes as well?

I think I see where you're going, but I haven't had time to think more about it.

The second part of your post, I am not sure if you're pointing out a (another?) problem or providing me with an optimization smile.png

Thank you for your comments!

RobTheBloke
RobTheBloke

He seems to be saying that you should always use matrices because RSR cannot be represented by SRT, which is simply not true. SRT * SRT * SRT can represent RSR just fine, not to mention the fact that your DCC package is going to be authoring data in SRT anyway, so why bother worrying about an edge case that will never happen?

Kwizatz
Kwizatz

I see, that's what I though, I am still in need of some time to reassess non uniform scaling, but with uniform scaling it seems fine to just multiply the scaling factors, since scale would be the same in all directions.

And yes, this is going to handle mostly DCC'ed meshes and animations, the 'optimization' in fact came about because of how I was doing animation interpolation.

Thanks again!

alvaro
alvaro
Just drop non-uniform scaling: The notion of what constitutes a non-uniform scaling is not an intrinsic feature of the transform (in other words, it depends on the coordinate system used), which is the root of a lot of problems. I don't know what people use non-uniform scalings for anyway.
RobTheBloke
RobTheBloke
If you want to take account of non-uniform scaling, you can simply do the following:

transform = [S] * [R] * [IS] * [T]

Where IS is the inverse parent scale, which is pretty much all that most dcc packages do....

Certainly maya applies that transformation for all joint nodes, but geometry leaf transforms usually just use a plain SRT.

I don't know what people use non-uniform scalings for anyway.

Speak to an animator, and they usually find a million reasons to support it. A common trick is to apply non-uniform scale on leaf transforms within a skinned mesh to prevent collapsing elbows, or to simulate the effect of muscles bulging. I've even seen rigs that have made use of excessive amounts of animated scaling for classic-cartoon-like squash and stretch. It does have its uses, which is why animators make so much use of it in maya et-al. A seasoned animator in the games industry may not use it at all, but that's usually due to them having been pre-conditioned to not use scaling on pain of death, not because it has no use.
alvaro
alvaro
I bet animators could also make use of shearing transformations, which just means that they should be given access to general affine transformations, not just movements (rotation + translation) or a combination of movements and uniform scalings. The only problem with that is what to do about interpolations.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.