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…
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:
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)^Nis matrix exponentiation: multiplyNcopies of(matrix)together in sequence.For the cumulative case, your OQ starts with a unit matrix, multiplies it by the small rotations
RxandRyin succession; yourrotationis equal to my(Rx*Ry). It then multipliesworldMatrixby 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 rotatingtotal_angle/total_framessequentiallyframetimes, 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 * RyandRy * Rxapproaches 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 NRx‘s to the one side is effectively a bubble sort: you’ll need O(N^2) swaps to do it….