Wednesday, July 2, 2014

The Simplified Quaternion Solution

Continued from the previous entry.

It turns out that the Euler Angle Order changes from the last blog post had a bug, causing the heads in Grim Fandango to not point in the right direction. This was odd because I had shown that the output was the same with both the original approach and the new Rotation3D approach, so what was the bug? After some time in my debugger, it turns out that the original implementation maintains the values in the matrix for position (the last column in the 4x4 rotation matrix) when building a new rotation. My new code simply discarded these values. With this changed in PR #944, the heads rotate properly in Grim Fandango again! Sorry about that oversight!
Manny is looking in the right direction again!
With Euler Angle Order taken care of by the changes in the Rotation3D class, I then focused on finishing the new Quaternion implementation without the added complexity of Euler Angles. The few remaining uses for Quaternions are largely in the EMI portions of the engine, which helps to reduce the impact that this patch has on the other supported games.

In the change set for improving Quaternions, I removed the direct implementation of conversion from Euler Angles to Quaternions. I then replaced it by creating a rotation matrix from the angles using the  new functionality we added to Rotation3D and then converting that to a Quaternion. Using this approach, there is only one implementation of Euler Angle conversion instead of the two (different!) versions that were in ResidualVM previously. I also added new functionality useful for Quaternion work and cleaned up the Quaternion class by improving the comments and fixing style issues. This work is in PR #943.

With a working Quaternion implementation, we can now finally work on fixing attaching and detaching for good! From the work before, I had thought that the position was computed properly. Things seemed to be positioned properly, Guybrush rode the manatee and there was no problem, right?

Well, testing with more complicated position vectors has shown that the code here isn't exactly right! Why didn't I notice the problem before? The examples that we used to demonstrate attaching/detaching like the candles, the raft and the manatee all have one attached part that's zeros or have values that are the same, hiding the problem.

So, what's a good vector for testing? Instead of using something from the game, we'll test with a vector that has different components for all of the vector elements. The following table shows the three actors we'll be testing, with the position and rotation vector for each:

actor1
Position(1,4,5)
Rotation(5,15,25)
actor2
Position(3,-1,-2)
Rotation(15,25,35)
actor3
Position(-3, -4, 2)
Rotation(12,16,18)

It would be easy to write a script that sets up this scenario, but instead, I've made it so that tests can be run on objects from the GRIM engine in ResidualVM, as discussed in the previous post. In the tables below, I've attached these actors, with actor3 attached to actor2, who is attached to actor1.

actor1Retail OutputResidualVM
getpos(1, 4, 5)(1,4,5)
getworldpos(1, 4, 5)(1,4,5)
getrot(5, 15, 25)(5, 15, 25)
actor2Retail OutputResidualVM
getpos(0.854459, -6.7806, -5.41233)(0.437805, -7.30678, -4.7349)
getworldpos(3, -1, -2) (-0.746586, -3.64594, 1.19355)
getrot(3.98862, 13.1276, 7.01816)(15, 25, 35)
actor3Retail OutputResidualVM
getpos(-6.7911, 1.63313, -0.462462)(-1.46815, 1.76189, -0.770648)
getworldpos(-3, -4, 2)(0.271631, -2.42397, -0.629527)
getrot(6.82211, -9.04857, -16.7055)(12, 16, 18)

Due to the influence that the rotation of the actor has on the position, I decided to fix the rotation first, then investigate if the position code needed to be modified further. In this next table, the actors' rotation is checked between the Retail version and ResidualVM:

actor1Retail OutputResidualVM
getrot(5, 15, 25)(5, 15, 25)
actor2
getrot(3.98862, 13.1276, 7.01816)(15, 25, 35)
actor3
getrot(6.82211, -9.04857, -16.7055)(12, 16, 18)

So, what does this tell us? Well, we need to change the rotation from a global rotation (the original angles) to a relative one (relative to the rotation of the parent object). How do we do this? By removing the rotation of the parent from the child. With Quaternions, there's no subtraction operator. Instead, we invert the rotation we want to remove, then combine it with the current by multiplying both rotations to find the difference.
This difference is our new rotation. Unfortunately, it was still wrong! After some testing and checking against the retail version, I found that the Euler Order was incorrect. Changing the Euler Order to XZY and swapping the input parameters around gets us here:

actor1Retail OutputResidualVM
getrot(5, 15, 25)(5, 15, 25)
actor2

getrot(3.98862, 13.1276, 7.01816)(3.98862, 13.1276, 7.01815)
actor3

getrot(6.82211, -9.04857, -16.7055)(2.79709, -9.00632, -16.1663)

Well, it's an improvement. The angle is now calculated properly for the first and second actors. So, why didn't the third attached actor come out correctly? For quaternions, the order of multiplication is not commutative. As it turns out, the order that the parent rotations were being applied was backwards, resulting in an incorrect rotation quaternion. After fixing this, the result matches the retail version. This was submitted as part of PR #948. In the next post, I'll discuss fixing the position and the changes required to make it work correctly.

No comments:

Post a Comment