Friday, April 12, 2013

Height Map Hell

One of the more complex areas of road data is the way in which setting the road height is handled. Each OutRun stage has 1948 positions that correspond to the path the level takes. We previously saw how a series of tables reference these positions to control aspects like the road width. This same table is also used to configure the height.

One of the entries in the table denotes whether the entry relates to a width change or a height change. Height changes are more complex than width changes. Each height change points to an entry in a separate table of 225 height maps.

So for example, on Coconut Beach at road position 1 the table references height map 211. In the diagram below, taken from my recent work on LayOut, you can see the data contained in this segment and a crude visualisation of the height map on the right.

The interesting thing is exactly how these maps correspond to a position in the level. We know when they are applied and therefore the start position of each height section, but how long do they last for? If the above map starts at road position 1, where does it end? 

The answer is relatively complex, as the height data you can see in the above table varies in length depending on the step adjust and multiplier values. Changing these values can either compress or expand the duration of the same height map. The road data isn't fixed to a particular road position increment. In fact in this example, changes in height are parsed through different multipliers to the straight sections and the end result would look something like this, presuming a start of road position 0. 

So here you can see the length of each data section in this single map and the road position it corresponds to at the bottom. I won't delve into the algorithms that map this data here, as it will probably confuse matters. I haven't yet visualized this in the editor itself. But we're starting to understand how these values correspond to a particular road position. 

Think of the actual values (0, 576 and -576) as the angle or steepness of the height section. And then the length pretty much specifies how high or how long that section of slope lasts for. 

To make things more complex there are four distinct types of height map in OutRun. Each type is rendered with a completely separate customized routine executed by the sub CPU that handles the road layer. Type 1 is the most common and shown above. 

Type 2 is more simple and used to create very long hills. It pretty much sets an angle and specifies a length or delay value. This is used at the point of the sharp chicane in Coconut Beach.

Now with Type 2, actually mapping this accurately to the road position gets interesting. Due to some quirks in the game code, the length of these sections actually depend on how fast you're driving. OK, you're not really going to notice this at normal speeds. But if you drive slowly, you can exploit a bug that pretty much elongates these sections way beyond where they should be. 

Here we are elongating the climb at the end of the Chicane. I'm driving slowly, and admittedly I've frozen the game timer. If we drive slow enough the hill can pretty much last right through to the end of the level:

OK, this is slightly silly stuff. But the point is that there is not a direct mapping between level position and the height map data. It's rather loose. So creating an editor where we can definitely say "the hill definitely ends here" or "this peak is exactly in line with this sign" simply isn't possible. The game code doesn't work in that way and there will always be ambiguity in some cases.  

Type 3 is a combination of Type 1 and Type 2. It's essentially some normal height data with a delayed section in the middle. The length of the section is handled differently again but is hard coded this time. Quite why, I don't really know. Typically a height entry will correspond to 8 road positions. This section isn't commonly used. 

Type 4 performs horizon y position changes. This can be seen at the start of Gateway, when the horizons level is raised. The only data attached to these entries is the new horizon position and the speed at which the change should happen.

It should be pointed out that the original assembler for this area of the game is horrendous and mildy terrifying.Therefore nailing all this detail down took a while, and unfortunately came at a time when things have been pretty busy for me. So apologies for the lack of exciting updates lately. 

In addition to this, I needed to expand my QT knowledge significantly to visualize this effectively in the LayOut editor. I implemented the ability to import the existing OutRun height map into the editor and display it in a logical tree format. 

There is still more work to be done, including basics like editing and creating new height map entries, although for a casual user, the existing maps should suffice. Once this is done, it will be time to move onto the fun stuff - adding level objects!

On Cannonball, a couple of people have kindly submitted changes that I will look into including in the future. The first is some control improvements from James and there is also some initial OpenGL rendering work from Legooolas, which will enable faster screen stretching/resizing than the current software approach. I'm holding off on Cannonball work for now and the next release is likely to be in conjunction with LayOut. 


James said...

Thanks for the update. Crazy how complicated this is.

Any chance I could get a copy of the updated video code? I'm playing around with some video filters and if the video code itself is/may get redone it would be helpful to see it sooner rather than later. If not, so be it.

Thanks again for your efforts.

yt said...

I've sent you the code via e-mail so you can take a look.

And yes, if you were to suggest that all this is more complex than a 3D game that simply used a model for the track, then you might be right. ;)