Sunday, April 27, 2014

Rotation!

Continued from the previous entry.

Before digging into the Quaternion implementation, I first inspected the retail version of EMI again to verify its approach to applying the rotation angles.

In the scene 'mig', I moved Guybrush to the position (1,0,-7) so it was easy to see the result of rotation. Next, I set the rotation to (0,0,0), resulting in the following, which represents our origin pose:
The origin pose for all of the rotation tests
To make sure that nothing else is interfering, I'll set Guybrush back to this rotation before each operation. Next, I set Guybrush to 90 degrees for each of the rotation angles to determine the name for each principle axis:
Demonstrating the principle axes
  • guybrush:setrot(90,0,0) - Pitch (rotation in the Y-Axis)
  • guybrush:setrot(0,90,0) - Yaw (rotation in the Z-Axis)
  • guybrush:setrot(0,0,90) - Roll (rotation in the X-Axis)
We now know which axis is which in the setrot() method. Using this information, we'll determine what order the rotations are applied by combining the principle rotations:
Combined rotations on the principle axes
Which is produced by the following rotations:
  • guybrush:setrot(45,45,0)
  • guybrush:setrot(0,45,45)
  • guybrush:setrot(0,45,45)
  • guybrush:setrot(45,45,45)
From this, we can say that the setrot() method's arguments are definitely Pitch, Yaw, Roll, in that order. From the combined rotations, it appears that the rotations are applied in the order ZXY (by axis). With this information, I can ensure that the Quaternion implementation is using the correct rotation order.

Thursday, April 24, 2014

Look at what arrived in the Mail today!

Pirates! Adventure! Monkeys! Exile!
I haven't had a chance to finish my work on Quaternions, due to my end of semester responsibilities, but I did get a copy of Myst 3 and EMI for PS2. Awesome! Now I have all of the supported games in ResidualVM, for testing after large changes to the engine.

Tuesday, April 22, 2014

Fixing Attaching and Detaching For Good!

Continued from the previous entry.

In the previous entry, we identified a problem with attaching and detaching actors in a scene. In this post, we'll look at the solution to the problem and then adapt the solution to the ResidualVM code.

In graphics, we usually think of posing an object as three rotations around the three axes, X, Y, and Z. These rotations are referred to as Euler Angles. Unfortunately, this isn't always the best approach, because while this rotation is easy to conceptualize, the result is sometimes ambiguous depending on how those angles are interpreted. So, while the EMI engine uses Euler Angles when interacting with the user, internally, the engine uses Quaternions. While I won't go into a discussion of Quaternions vs. Euler Angles, or the math behind them, understand that the output generated by the engine is going to involve the conversion of Euler Angles to Quaternions, some computation, then back into Euler Angles.

After examining the disassembled EMI source code and confirming that the engine did use Quaternions internally, I set up some test code using the Irrlicht Quaternion implementation because it was easy to use and easy to test different configurations with. The result of my testing can be found here. This implementation correctly computes the angles as reported by the EMI retail engine, as recorded in this spreadsheet.

Essentially, after some testing, I found that the EMI retail engine computes the new rotation angle by finding the Quaternion of the actor we're attached to, finding the inverse, then multiplying that by the Quaternion of the actor that's being attached. For detaching, the operation is reversed, where we take the attached actor's Quaternion and multiply that by the inverse of the attached actor's Quaternion.

So, what do we need to add to ResidualVM? In ResidualVM, the Quaternion implementation requires two new methods:
  • getInverse() - Finds the Inverse of a Quaternion
  • toEuler() - Returns the Euler Angles from a Quaternion
With these new methods, we can now implement the correct code for attaching and detaching actors in actor.cpp. However, I discovered that the ResidualVM Quaternion code wasn't making much sense and the result was still incorrect. After discussing it with Botje, wjp, somaen and klusark on IRC, I decided to make the Quaternion implementation and usage consistent. This work will be described in the next entry.

Saturday, April 12, 2014

The Attached are Attached

After the Manatee's Ride, I hoped that all of the attachment issues were fixed. Unfortunately, that's not the case. Another, more complicated attachment situation presents itself when Guybrush is arriving at Pegnose Pete's house. In this scene, Guybrush puts down the raft pole. Here, the pole is attached to Guybrush, who is in turn connected to the raft. When the pole is detached, the detached location doesn't match the retail version, resulting in the scene below:
How did the Pole get up there?
So, what's the problem in ResidualVM? A series of scripts generating scenarios indicated that the current code for detaching actors was incorrect. Reversing the procedure that was worked out in the Manatee's Ride resulted in the correct detached position for actors connected to another actor.

Great, now the above situation worked correctly right? Nope. It turns out that the situation is more complex when you've got a situation like the scene from the Mysts O' Time with three actors attached. We'll start by checking the values when everything is attached after Guybrush gets on the raft in the first setup of the scene "mot":
swampraft Retail ResidualVM
getpos (-0.109778, -0.12, -0.437341) (-0.109778, -0.12, -0.437341)
getworldpos (-0.109778, -0.12, -0.437341) (-0.109778, -0.12, -0.437341)
getrot (0,733,0) (0,13,0)
guybrush Retail ResidualVM
getpos (0.4, 0.25, 0.4) (0.4, 0.25, 0.4)
getworldpos (0.359951, 0.13, -0.137574) (0.36995, 0.13, -0.137573)
getrot (0,0.252186,0) (0,0,0)
mot.pole Retail ResidualVM
getpos (-0.0222, 0.0287, 0.147) (-0.0222, 0.0287, 0.147)
getworldpos (0.513002, 1.36408, 0.350046) (0.510769, 1.36408, 0.350696)
getrot (-13.8, -87.5, 0) (-13.8, -87.5, 0)

While there are some differences, these results match up pretty well. Let's detach the pole from guybrush and check the coordinates again:

mot.pole Retail ResidualVM
getpos (0.513002, 1.36408, 0.350046) (0.411437, 0.2787, 0.548226)
getworldpos (0.513002, 1.36408, 0.350046) (0.411437, 0.2787, 0.548226)
getrot (129.963, -25.8091, 0.0515321)(-13.8, -87.5, 0)

There's a quick fix for the world position, using the result from getworldpos gives us our new coordinates. This makes sense as we don't want to move when detached, so the world coordinates should become the new position. The rotation is more problematic, how did we get there? The resulting rotation seems to be the result of converting from providing the actor's pose in the local reference basis to the world reference basis.

Interestingly, detaching all three actors, then re-attaching them results in an incorrect placement of the pole object, indicating that there's probably some bugs in the original implementation. This appears to have been worked around by constantly setting the position and rotation in the game scripts.

To figure out exactly what was going on, I tested attaching three actors in different poses and recorded the final attached values. Then, I detached the third actor and recorded those values as well. The result of this experiment is recorded in this spreadsheet. Of interest, I found that the position of the actor does not change the resulting orientation, so the only values recorded are the rotation angles retrieved with the :getrot() method. In the next blog post, we'll interpret these values and figure out how EMI actually calculated the attached and detached position angle.

Sunday, April 6, 2014

Making Patches with diffr and patchr

ResidualVM comes with two tools we can use to modify scripts while running a game in the engine. These tools are diffr and patchr, and can be found in the residual-tools repository.

diffr takes two files, and prepares a binary difference patch between them, outputting the result into a .patchr file. patchr takes the original file and the patch, and outputs the patched file. Let's say we've made a fix to the game logic that we'd like to ship with ResidualVM. In this example, I'm replacing the call to ReadRegistryValue in the EMI Demo so that instead of trying to read the value SfxVolume from a non-existent registry and segfaulting (remember that bug?) the script instead reads from system_prefs.cfg.sfxvolume, which is implemented properly in ResidualVM for EMI.

Once we have made the desired change to our script, the first step is to compile a new script. If we can, it's desirable to base this script on the previous script to reduce the amount of binary changes for patchr. Note that the luac referenced here is the one distributed with residualvm-tools, a regular copy won't work!
  • luac -b <old compiled script> -o <new file output> <script to compile>
With the compiled script, we'll now ask diffr to make a patchr file for us, describing the binary differences between the two files:
  • diffr <old compiled script> <new compiled script> <patchr file>
Now, we need to add this to the file containing the patches used at runtime. In the source directory, there is a folder that contains the existing patches. It can be found here for EMI's patches: dists/engine-data/patches/emi. Once we have generated a patchr file, move it into this directory. Then, from dists/engine-data we'll generate a new copy of residualvm-emi-patch.m4b containing our patch:
  • mklab --emi patches/emi residualvm-emi-patch.m4b
Now, what happens if someone has already made a patchr for the file we'd like to change? In this case, we'll patch the binary file using patchr, then delua it like normal so we can make our changes:
  • patchr <old file> <new output file> <patchr file>
Remember that when building the new file and generating the patchr file, the old file is the original file from EMI, not the one we generated using patchr.

In this way, we preserved the changes already made, and can safely overwrite the patchr file when making our modified residualvm-emi-patch.m4b.

As an added bonus, I've also written a script to let you get a regular old patch file from a patchr file. Simply pass the file to be patched and the patchr file, and the script will spit out a text patch that describes the changes made. Obviously, this only works if your patchr file is patching a Lua script. Here's how to use it:
  • patchr_diff _options.lua _options.lua_1.patchr

Saturday, April 5, 2014

Mystery of the Non-Vanishing Pretzel

You know that feeling when you reach into a bowl of pretzels and munch down, enjoying that savory jerky flavor. Then, when you reach into the bowl again, you find that the old pretzel that you just ate is still stuck to your hand...
Mmm... Delicious Magical Jerky Pretzels, Once You Pop, You Can't Stop
Okay, so it's another bug! Looking into the script _props1.lua, we see the following sequence is run when Guybrush eats the pretzel:
  • The pretzel is detached from the bowl
  • The pretzel is attached to Guybrush @ wrist_l 
  • When the eating chore finishes, the pretzel has set_wear_chore(nil) run on it
It looks like the call to set_wear_chore(nil) makes the actor invisible. Let's try it out on the retail copy, using the debug window. Let's see if we can make the drunk vanish:
  • drunk:set_wear_chore(nil)
Abracadabra! Where Did the Drunk Go?
Presto! He's gone! If you'd like to set him back, you can do this:
  • drunk:default()
So, what happens when we do the same with ResidualVM?

Maybe if he doesn't move he'll be invisible too!
So what is ResidualVM doing wrong? Let's examine the Lua for set_wear_chore, a method of actor in _actors.lua. There's a lot going on here, so I reset the scene and then started stepping through each command in set_wear_chore using the retail version until the drunk vanished again. Luckily, it turns out that it's first action, the call to stop_all_chores, that makes the actor vanish!

This method is from the actor object as well, and the code for it is also in _actors.lua. Luckily, it's just a simple call to the Lua function StopActorChores, which if we follow it through, simply calls the stop() method on every one of the actor's chores. Before we get much further, let's take a stop ourselves to review what Chores are.

Chores are instructions that describe what the actor is supposed to look like, or what the actor is supposed to do. As an example, you can make the drunk look like Meathook by doing the following from the console:
  • drunk:set_wear_chore("meathook.cos")
The Drunk becomes Meathook!
They usually also contain rules for behaviors, like when Guybrush eats a pretzel (to use a topical example):
  • guybrush:play_chore("eat_pretzel")
In the retail version, this runs the chore, but here, it doesn't. This is probably another issue that needs to be looked into!

So, what have we learned? When the method set_wear_chore is called with nil, the actor's costume is stopped as well, which results in the actor not being drawn. In ResidualVM, the actor's costume is stored in a separate place from other chores and isn't stopped when StopActorChores is called. How do we fix this?

I decided that it would be easiest to just store if the wearChore is active or not. I also added a check to see if the wearChore was active in the draw method in costume.cpp to prevent the object from being drawn if the wearChore is inactive. Finally, I added a method to allow setting and getting this state variable. This was pushed in PR #858.

Friday, April 4, 2014

Getting Back in the Saddle

Continued from the previous entry

We now have a pretty good idea of what the problem is. In all of the previous cases, when we apply rotation to the actor before attaching, we find that the actor position (and world position) are calculated incorrectly. We also found that the rotation was not applied to the attached actor, which resulted in incorrect rotation values as well. All of these issues were only present in the attached actor, the actor that we attached to was fine.

Here's the patch I came up with to fix the problem, and an update that removes need to invert the position before saving it.

To fix attaching with rotation, I noticed that the current code didn't actually transpose or rotate the actor's position based on the rotation of the object we're attaching to.

Why didn't getWorldPos() do this? When getWorldPos() is called, the actors are not attached, so they just return their own position!

While this fixes the manatee and the candles and matches the output of getpos() in the retail engine, the rotation and world position components are still broken, as is detaching actors. These will need to be addressed in future posts, but for now, the bug is fixed!

Giddy-up Little Manatee, Giddy-up

Monkey Combat Fill in the Blanks Sheet

They really should have provided a pad of these with the game:
Thanks to the person who originally made these!

Detour: Fixing the Segfault - Part 3

Continued from the previous entry

In the last post, we learned that the code for skipping the introduction movie is different between the demo and the full version of EMI. In this post, we'll delve into how this works in the demo and see if we can fix the problem in ResidualVM.

I first tried to inject my Z button debug script into the main demo file with all of the scripts (MagDemo.lab), but I was only able to get a dialog box in ResidualVM. Undaunted, I tried to modify the _system.lua script to load different scenes depending on the state of the system.buttonHandler variable, which does key mapping. In short, I wanted to see if the default button handler was loaded during the intro cut scene. However, whatever I tried to do, nothing was changing! Even things like removing BOOT, the first function called in the game, weren't having any effect.

As it turns out, the demo has an extra lab file (i9n.lab) which contains a slightly different version of the _system.lua script. This file is apparently loaded after or perhaps instead of the _system.lua script in the main object file. With this sorted, I went back to testing to see what the state of system.buttonHandler was.

As it turns out, in ResidualVM, the default handler is set, likely by the _system.lua script, when it loads the default controls in _control.lua. In the demo, there is no handler! I checked for this using the following test:
  • Inserted code after the call to StartMovie("intro") to save if the buttonHandler was set or not
  • Changed the set that you switch to depending on the state of that variable.
It is likely that the demo uses custom code for handling ESC during the movie. This is also why ResidualVM crashes with a segfault when you press F1, at this point in the demo, nothing is watching for F1. The original designers missed this because it's not possible to reach in the retail demo. It also appears to resemble what happens in the full game as well, with ESC being the only active key while movies are playing.

That said, how can we fix this in ResidualVM?

A simple patch was pushed, but I'm not sure this is the right way to do it. If not, there will be a follow up post describing how to fix the issue the right way!

Tuesday, April 1, 2014

That Menacing Mechanical Manatee

Continued from the previous entry

After the initial excitement of fixing the bug, Inguin remembered that the Manatee was the reason why that code was written in the first place. We have to try it out, so let's get to the MicroGroggery and try it out:
  • dofile("_jumpscripts.lua")
  • jump_script("250")
  • switch_to_set("mig")
Great, we're there! After a brief conversation with the barkeep, we get to ride the Manatee. And then things go a little bit wrong:

Air Riding the Manatee!
It turns out that the Chandelier wasn't a great test of the position code. The base actor was at (0,0,0) and had no rotation. The Manatee (or more specifically: mig.manatee_rotator) has a rotation before we attach Guybrush's actor to it. So, there's a problem with attaching actors when there's rotation. Let's do some investigation and see what the behavior of the retail engine is, as compared to the current code in ResidualVM.

The configuration for all of the tests start with this setup:
  • Actor1 is located at [1,2,3]
  • Actor2 is located at [2,4,6]
  • Actor1 is rotated to (0, 90, 0) using setrot
  • We call Actor1:attach(Actor2, nil) to attach the two actors
Once these steps have been completed, I looked at the information we can get from the Actors:

Command ResidualVM Retail
Actor1:getpos() (1,2,3) (1,2,3)
Actor1:getworldpos() (1,2,3) (1,2,3)
Actor1:getrot() (0,90,0) (0,90,0)
Actor2:getpos() (1,2,3) (-3,2,1)
Actor2:getworldpos() (4,4,2) (2,4,6)
Actor2:getrot() (0,0,0) (0,-90,0)

So, what does this tell us? For Actor1, the attaching doesn't change his position or behavior and both engines do the same thing! Actor2, who was attached to Actor1 doesn't fare quite as well. Starting with getrot, we see that the retail engine shows the angle relative to the attached actor. The world position from getworldpos is the original position before we attached the actors, while the position reported by getpos is wrong.

Let's try another test with this setup:
  • Actor1 is located at [1,2,3]
  • Actor2 is located at [2,4,6]
  • Actor1 is rotated to (0, 90, 0) using setrot
  • Actor2 is rotated to (20, 0, 0) using setrot
  • We call Actor1:attach(Actor2, nil) to attach the two actors
What happens this time?

Command ResidualVM Retail
Actor1:getpos() (1,2,3) (1,2,3)
Actor1:getworldpos() (1,2,3) (1,2,3)
Actor1:getrot() (0,90,0) (0,90,0)
Actor2:getpos() (1,2,3) (-3,2,1)
Actor2:getworldpos() (4,4,2) (2,4,6)
Actor2:getrot() (20,0,0) (90,70,-90)

Finally, let's look at what happens when we detach Actor2 from Actor1 using the second example:
  • Use the setup from the previous example
  • Then a2:detach()
Here's what happened this time:

Command ResidualVM Retail
Actor1:getpos() (1,2,3) (1,2,3)
Actor1:getworldpos() (1,2,3) (1,2,3)
Actor1:getrot() (0,90,0) (0,90,0)
Actor2:getpos() (4,4,2) (2,4,6)
Actor2:getworldpos() (4,4,2) (2,4,6)
Actor2:getrot() (0,0,0) (0,0,-20)

We can now make a few guesses as to what's going wrong. In the next post, we'll figure out how to fix the problem!

Fixing the Positions

One of the requirements for the 2014 GSoC is to write a patch and complete a pull request. While we've finished a small fix with the segfault, I wanted to take on something a bit bigger for this pull request. Unfortunately, I didn't have enough time to figure out all of the Actor offsets, embedded functions and structures to complete the SetActorLocalAlpha function for this deadline. Instead, I decided to try and figure out why the chandelier lights were being rendered in the wrong place inside the Governor's Mansion. Unfortunately, this ended up being a much bigger problem than anticipated. Here's what I've found, along with more information about what the original engine does.
Guybrush burns, burns, burns, in a ring of fire
To start with, it was tedious to replay the same part of the game over and over again to get into the mansion. To avoid this in ResidualVM, we can use the debug console to run a script to change the current setup. To do this, we must first load the file that contains this script. Bring up the console with CTRL-D and type:
  • lua_do dofile("_jumpscripts.lua")
Press ESC, to close the console, and this code will be executed. Getting back into the console again, we run the jump_script function:
  •  lua_do jump_script("mansion interior")
Press ESC, and we'll be warped into the Governor's Mansion setup, cool! The jump_script  takes care of setting all of the game states as well, you can consider them to be like bookmarks in story. From one of these bookmarks, we can also jump to specific setups using the switch_to_set("setname"), where setname is the name of the set you'd like to switch to.

Unfortunately, this trick doesn't work in the retail build because we don't have a console to type Lua into. Thanks to klusark, who recently fixed mklab, we can now unpack and repack the .m4b file and insert our own code into the game's data. I'm using two scripts, one which extracts a Lua script, and another that pushes in the new script and rebuilds the .m4b file.

I used these scripts to unpack the _control.lua script and wrote this patch (containing only my own code, so it should be okay to post) to add a dialog box for typing in Lua to be executed directly. This mimics the console in ResidualVM, but works in the retail build as well. You enter this console by pressing the z key. We can  also print variables as the message line for the dialog box by assigning a value to the dd variable.

Now, let's take a look at what's going wrong with the chandelier lights. Examining gmi.lua, the script that contains the setup for the Governor's mansion, we see that the main chandelier actor is located in the variable gmi.chandelier. There's another actor called gmi.chandelier2 that is attached to the first chandelier variable that actually has all of the candle actors attached to it. So, let's explore these variables and see if we can figure out what the problem is.

Using the new console tool, we can press z to bring up our debug console, switch into the Governor's Mansion setup and print out the position for the chandeliers:
  • dofile("_jumpscripts.lua")
  • jump_script("mansion interior")
  • dd = gmi.chandelier:getpos()
  • dd = gmi.chandelier2:getpos() 
Here's the output from the retail version for gmi.chandelier2:
Debug output showing the position vector for chandelier2
Here's the output from ResidualVM:
Debug output showing the position vector for chandelier2
So, the chandelier2 variable is in the wrong position, with the y axis being -1 instead of 1 as in the retail version. Let's see if just moving chandelier2 to the right place fixes the problem:
  • gmi.chandelier2:setpos(0.0, 1.0, 0.0)
The chandelier lights are in the right place!
Indeed it does! So, what broke it? After inserting some more debugging print outs, I found that the actor.attach method was the culprit that was adjusting the position. Specifically, the call to the AttachActor lua method was causing the problem, which leads us to the method:Actor::attachToActor. In this function, the attached actor's position is modified by subtracting the base object's position from the attached actor's position. Checking against the retail version, it seems that the subtraction should be reversed, with the attached actor's position subtracted from the object it's being attached to.

I injected a new lua script with a series of attachments and compared those results with the retail engine and things looked perfect! Or did they...