Got sidetracked a bit. The question that sidetracked me is an oldie but a goodie.
Wolverine@MSU wrote: ↑Mon Aug 05, 2013 7:03 amOnce you finish that project, perhaps you could turn your enthusiasm toward identifying where heightmap info is stored in the .gmp files. I've always thought it would be useful to have utilities like PJay's BMP2GMP / GMP2BMP duet to inject/extract heightmap info from a map.
I found it. Even better, it's not hard to find once you know what you're looking for. Milo's notes are a bit confusing (may have been WIP) but I've nailed down the format in terms average mugs can understand.
I made a test heightmap: 65 x 65 in size (smallest the editor will accept) and with a constant height rim 4 pixels wide, surrounding a central ocean. This meant there were only going to be 2 known values in the heightmap data, so easy(ish) on the brain. The raised rim was coloured as RGB(0,0,248), which on the usual "1 internal height unit = 1 metre" scale means an altitude of 248 metres above sea level.
But, it turns out that RT3 does that multiplication by 0.7 before storing the height values in the .gmp, so the initial 248 ends up being stashed as 173.6. It gets stashed as a four byte float, so the hex is actually 99 99 2d 43. So that's a point to note: if you happen to be looking for a non-zero height value in the heightmap data, its value will be recorded as 0.7 of whatever your .dbf gives as the nominal height for that particular colour on your DEM/TGA.
Anyway, by referring to Milo's notes, scratching my head, looking around, and ignoring Milo's notes where they didn't make sense, while taking notice of them where they did make sense, I got a result. The heightmap data starts just after the end of the locomotives list.
The locos are 65 bytes each. Next shot shows the last loco's bytes with extra crimson highlighting.
Straight after the last locomotive, there are 16 bytes (yellow) with weird stuff in 'em (no idea what it is at the moment). These are followed by 8 bytes, which record the width and height of the map, plus 1. In other words, they are recording the number of vertices coded by the heightmap instead of the number of pixels on the map (verts = pixels + 1 for any row). These width and height values are integers (not floats) in RT3's standard little-Endian hex. They are followed by another 8 yellow bytes of more weird stuff. After that, it's straight into the heightmap (highlighted in red).
The good news here is that the weird stuff in yellow appears to be a standard chunk of heading code. It's exactly the same in my 64x64 test pool and in my 512x448 New South Wales map, so should always be easy to find via search. The bytes in both cases are (dd 32 00 00) (e2 2e 00 00) (f9 2a 00 00) (ba 0b 00 00) in front of the height declaration, and (fa 2a 00 00) (fc 2a 00 00) between the width declaration and the start of the heightmap data.
The heightmap data is coded to have a four byte padding, all zeroes, at the end of each row of verts. The verts are counted left to right, starting at the bottom left corner of the map. After every 65 verts on this small test map, we get this (dark greenish - four zeroes row ending):
After the last row of verts and its finishing four zero padding, there's a tricky bit thrown in. It's RT3. There have to be tricky bits. There are
another 65 instances of four bytes of zero tacked on the end, after the padding at the end of the last row of heightmap verts. So in the screenshot above, the actual heightmap data ends with 99 99 2d 43 (vertex height) then 00 00 00 00 (standard row ending), then the last lot of zeroes is added on.
Edit: Correction. I made a mistake here. Got it figured out now. Ignore the part in small italics.
Note that while the number of bytes in each row of verts will obviously depend on the width of the map, this end chunk of zeroes is independent of map size. It's always 65 chunks, of four bytes each, all zeroes. After that you'll see ED 2C 00 00, which marks the start of what Milo calls the "ground map".
The correct version goes like this: The number of zero bytes between the last heightmap line ending, and the ED 2C 00 00 hex at the start of the "groundmap", is always 4 + 4*((width of map in pixels) + 1).
Which is another way of saying there are four bytes of zero for each vert in a row (ie: 00 00 00 00) and then there is another of those (00 00 00 00) as a standard line ending.
So if your map is 640 px wide, there will be 2,568 bytes of zeroes between the end of the last row of height verts and the start of the groundmap, because 4(640 + 1) = 2564, and then you add another 4 to that for the standard line ending, and you get 2568.
For a map 512 wide (like my NSW map) there are 2056 bytes of zeroes: 4(512 + 1) = 2052. Then add another 4, and you get 2056.
I've cross-checked all of this by doing the same searching and bookmarking in the hex of my New South Wales map (edit: went and checked a few more maps, ergo the correction) and it all works (really does now). Even ran a few checks on which vertex is the last land one going east, and which is the first ocean, and those values match in the TGA heightmap and in the hex. So it all seems solid.
Edit: Just thought of something else. The number of bytes in the actual heightmap block (before the extra zeroes are tacked on the end) is always equal to 4*[((Map width in pixels) + 2) * ((Map height in pixels) + 1)]
Why does it equal that? Because the number of vertices is always pixels + 1, then you have the four byte 00 00 00 00 line ending for each row, so total number of bytes per horizontal row = 4(pixels + 2). Vertically, number of rows = pixels + 1, so multiply that figure by the previous figure. Then multiply the lot by 4, because there are four bytes for each heightmap value or line ending.
This can be used to check that you have found the exact end of the heightmap verts. If your map has some ocean in the top right corner, the last heightmap values will all be zeroes. That makes it hard to tell where the heightmap stops and where the extra zeroes are tacked on. Your hex editor should give a readout of the number of bytes in any selected block, so that can tell you exactly where the last vert is.
![thumbs_up !*th_up*!](./images/smilies/ok.gif)