Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiplying matrices

What is the difference between the two pieces of pseudo-code?

// Multiplying a matrix by the difference between each frame
float difference = current - previous; // Time since previous frame
float angle = difference / 500;
matrix rotation;
rotation.RotateX(angle);
rotation.RotateY(angle);
worldMatrix *= rotation; // Note multiply

// Multiplying a matrix by the difference between current and start
float difference = current - start; // Time since first frame
float angle = difference / 500;
matrix rotation;
rotation.RotateX(angle);
rotation.RotateY(angle);
worldMatrix = rotation; // Note assignment

There are only very minor differences between each piece of code, but lead to big visual differences. The input looks like this:

Frame 1: Rotation = 1 radian
worldMatrix *= rotation;
Frame 2: Rotation = 1 radian
worldMatrix *= rotation;
etc...

Frame 1: Rotation = 1 radian
worldMatrix = rotation;
Frame 2: Rotation = 2 radians
worldMatrix = rotation;
etc...

like image 572
Mark Ingram Avatar asked Apr 30 '26 06:04

Mark Ingram


2 Answers

Actually, the result should be different (even if you aren't considering cumulative error, as above). The reason is that order matters in rotations: rotating about X, then about Y, is different from rotating about Y, then about X.

In matrix notation (as far as I understand your setup), you have the following ideal behavior:

let angle = (end - start)/500
  Rx = rotate.RotateX(angle)
  Ry = rotate.RotateY(angle)

then, foreach frame in (0..500):
cumulative: Rc = Rx * Ry * Rx * Ry * ... * Rx * Ry
               = (Rx * Ry)^frame
assignment: Ra = Rx * Rx * ... * Ry * Ry * ....
               = (Rx)^frame * (Ry)^frame

Some notes on the pseudocode: the convention here is that we multiply matrices from left to right (which means that points are row vectors). Also, in case it's not clear, (matrix)^N is matrix exponentiation: multiply N copies of (matrix) together in sequence.

For the cumulative case, your OQ starts with a unit matrix, multiplies it by the small rotations Rx and Ry in succession; your rotation is equal to my (Rx*Ry). It then multiplies worldMatrix by that matrix multiple times; this means that for any given frame, worldMatrix = initial_worldMatrix * (Rx*Ry)^frame.

For the assignment case, you compute the angle to be frame * total_angle/total_frames. This is equivalent to rotating total_angle/total_frames sequentially frame times, which is important because these small rotations are exactly the same as the small rotations used in the cumulative case. So, in the assignment case, your code is computing (Rx)^frame * (Ry)^frame, and resetting worldMatrix to that value each time.


The point is that these are different matrices; even with perfect math, they ought to look different.

Which one you should choose depends on what behavior you want. The cumulative version will closely approximate rotation about an axis diagonally between the X and Y axes; the assignment version acts like rotating gimbals instead.

If you do want the cumulative behavior, there are better ways than multiplying up to 500 matrices together (as mentioned above, your matrices will drift due to floating point error). Specifically, you can rotate 45 degrees about the Z axis, then rotate frame/500 about the X axis, then rotate -45 degrees about the Z axis, to get a similar effect.


To elaborate on the difference between the two cases:

In the cumulative case, you are rotating a little bit about X, then a little bit about Y, repeat many times. If the rotations are small, the result of combining the two small rotations will be a small rotation about some axis (if they aren't all that small, the axis might not be exactly between the two, but it will still be a specific rotation). The point is, if you repeat that pair of rotations, the result will be more and more rotation on that axis, whatever it is.

In the assignment case, you're doing all your rotation about X, then all your rotation about Y. This makes the rotations large, and that makes a difference. One way to visualize the difference is to imagine a set of coordinate axes: the large X rotation will rotate the original Y axis out of line, so that the Y rotation is applied differently.

In mathematical terms, the reason why there's such a big difference is that, in general, rotations are not commutative: in other words, order matters. Note that small rotations are approximately commutative (as the rotation angle approaches zero, the difference between Rx * Ry and Ry * Rx approaches zero quadratically -- cutting the angle in half reduces the difference by a factor of 4), but when you combine all the mini-rotations into two large ones, you're doing so much re-ordering that it makes a huge difference. Even if each individual swap (Rx * Ry -> Ry * Rx) has only a small effect, migrating N Rx's to the one side is effectively a bubble sort: you'll need O(N^2) swaps to do it....

like image 70
comingstorm Avatar answered May 02 '26 23:05

comingstorm


The difference seems to be that the first sample will alters the current world matrix by the rotation matrix. The second sample replaces the world matrix by the rotation matrix. You might not see any difference if no other operations have been applied to the world matrix before this one, but if any operations were applied before this, the second code sample would discard those.

The question is, do you want your changes to the world-matrix to be cumulative or not? The first code sample will give you a cumulative effect, the second will not.

like image 31
FrustratedWithFormsDesigner Avatar answered May 02 '26 22:05

FrustratedWithFormsDesigner