Flight Unlimited III high resolution terrain processing workshop

noncommercial FU3 support page - just to keep FU3 flying !

Analysis of the terrain elevation and classification


The second part of high resolution terrain model in FU3 is the large file in the regions geos folder named geotiles.tag. Inside this file, there are 303 distinct subfiles for the Seattle region and 252 for the Sanfran region. After all subfiles there is a table of content for each subfile entry.

The Seattle region has 100 megatiles going from aaaa to ajaj with 3 subfiles per megatile plus the blank megatile. The Sanfran region has 84 megatiles going from aaaa to ajak also with 3 subfiles but many of the megatile of the seaside are substituted by the blank megatile. In the Seattle region blank megatiles are never used inside the highres area.

the 3 .geo subfiles for each megatile are named L0aaaa.geo, L2aaaa.geo and L5aaaa.geo for the megatile aaaa.

There is also 1 set of .geo subfiles for the blank megatile named L0blank.geo, L2blank.geo and L5blank.geo

This blank megatile is used to simulate the north of the Seattle region looking towards Vancouvert and this megatile is the reason why when flying to the north, you see land in the sea area that desapear when comming nearer. I think this megatile should be modified for better results.

The level of detail for .geo is opposite that of textures .bin, .cpd (0 is lowest resolution, not highest)

There are not distinct .geo files for every level but level sharing
Geos level texture map levels Geosize structures Grid
0 7 and 6 266 1 5x5
2 5,4 and 3 14336 16 9x9
5 2,1 and 0 387072 1024 9x9

All these subfiles contains the elevations and classifications info of the megatiles.

Level 0 has 25 height fields ranged in a 5x5 matrix. The top (north) row and right (east) column are values copied from the megatiles next to the north, north-east and east of the current megatile. The remaining left-bottom part as 4x4 matrix has the data of the actual megatile.

The index sequence of the height fields start always in the bottom-left corner going from there to the right and then up to the top row.

The first Quad of the Quad array is the main quad of the entire megatile.

The next 4 Quads divides the megatile in a 2x2 matrix and

the next 16 Quads is the 4x4 split of the megatile. this is the same in all levels.

In level 2 only, there is an additional split of the megatile in a 8x8 matrix requiring 64 Quads more.

level 0 structure

In level 0 the heights of the megatile are defined by 4x4 squares, first as mean of the 64x64 original values from level 5 of a square area, but the world looks to flat and I change this to select the maximum value. Doing this the sight to distant mountains becomes better and for the near area, level 5 heights are used and the valleys are back again.

Height fields alone are not enough to define the terrain elevation in FU3. The Level 0 has also 21 Quads defining bounding boxes around a set of height squares defining the min, max and the max lod height error squared and inverted - but actually unknown how to compute it. There is not clear to what the height error is related, to the mean height ?

For a tottaly flat area the value is set to 1.00e12 and the bounding box min = height field value - 3 and max = height field value + 3. Computing back 1 / SQR(1.00e12) you get a value near 0 (1.00e-6) but not 0. If this value is set to 0, you get strange pictures in the 3-D-view of FU3.

The QuadLevel structure points to the Quads for the start of the 1x1, 2x2, 4x4 and 8x8 matrix. The pointer addresses are unusable, but computed back to the virtual address of the subfile, then all the pointers are correct, but in reality not at this addresses. I have changed it to relative pointers with 000000 as start of the subfile. FU3 don't care about this.

Raster of the Geos structures for a megatile:

Level Subfiles Quads/Subfile Heightfields/Quad Datapoints/Heightfield
01116 (4x4)4096 (64x64)
014 (2x2)4 (2x2)4096 (64x64)
0116 (4x4)14096 (64x64)
216 (4x4)164 (8x8)64 (8x8)
216 (4x4)4 (2x2)16 (4x4)64 (8x8)
216 (4x4)16 (4x4)4 (2x2)64 (8x8)
216 (4x4)64 (8x8)164 (8x8)
51024 (32x32)164 (8x8)1
51024 (32x32)4 (2x2)16 (4x4)1
51024 (32x32)16 (4x4)4 (2x2)1

Still open questions concerning the GeoTiles.tag file data:

    exact computation of the level 0 height values from the original heights
    exact computation of the level 2 height values from the original heights
    exact computation of the min,max height values for the bounding box
    computation of the max lod height error squared and inverted
    the meaning of the last 4 bytes in the header 0xde 0xad 0xbe 0xef
    the selection of the triangle split \ or / in a Quad
    the sequence of the megatiles in the file and in the table
level 0 attributes

level0,2,5

level 0 quads

subfile relations

 

Address
The Structure of the GeoTiles.tag file
0x0000



0x010f
Header: 272 bytes from 0x0000 to 0x010f

Pointer to the Start of Geos-Table (4 bytes)
unused space 0x00 (264 bytes)
4 unknown bytes 0xde 0xad 0xbe 0xef
0x0110
Geos files: levels 0, 2 and 5 for each megatile in any order
a level 0-2-5 set for 1 megatile has a size of 401776 bytes
Size in
bytes





34



24














24















50






168
Level 0 Geos - 1 subfile per megatile

Size: 266 data bytes
Total size with the header: 1*266+34=300 bytes


Name padded with 0x00
l0blank.geo : level 0 for the blank megatile
l0aaaa.geo  : level 0 for megatile aaaa

TileInfo
  typedef struct _TileInfo
  {
    WORD *hfield;  // pointer to height field, unused addr.
                   //   Region   Seattle       Sanfran
                   //   pointer  0x00EB0ED0    0x005735E0
    long row;      // row posts for hfield = 5
    long xdel;     // startpoint x inside region, negative for blank tiles
                   //   for aiah xdel=8,  for blank xdel=-1
    long ydel;     // startpoint y inside region, negative for blank tiles
                   //   for aiah ydel=7,  for blank ydel=-1
    long lod;      // base level of detail = 0
    long num_levs; // number of levs = 4
  } TileInfo;
        
QuadLevel (4 entries at 6 bytes)
  typedef struct _QuadEntry
  {
    QuadInfo *quads; // pointer to quad array, unused addr.
    WORD post;       // post separation
  } QuadLevel[4];

        Region   Seattle       Sanfran
        pointer  0x00EB0F02    0x00573612
        posts    4             4
        pointer  0x00EB0F0A    0x0057361A
        posts    2             2
        pointer  0x00EB0F2A    0x0057363A
        posts    1             0
        pointer  0x00000000    0x00000000
        posts    0             0
        
height fields (5 * 5 entries at 2 bytes)
  WORD heightfld[25];  // (height in ft + region sealevel) * 4 + classification bits
                       //    classification bits: 0 land, 1 water, 2 urban
                       //  1 ft = 0.3048 m  and  1 m = 3.28 ft
        
QuadInfos (1+4+16 = 21 entries at 8 bytes)
  typedef struct _QuadInfo
  {(\) or topright-bottomleft(/)
    float inv_err2; // max lod height error squared and inverted ?!?!?!
                    //    for a totaly flat area the value = 1.00000e+012
                    //    The lowest bit of the QuadInfo inv_err2 field is the
                    //    "split direction" bit which determines if the quad is split
                    //    into triangles topleft-bottomright(\) or topright-bottomleft(/)
    WORD min;       // min height for boundingbox, for the blank flat area: heightsfld-3
    WORD max;       // max height for boundingbox, for the blank flat area: heightsfld+3
  } QuadInfo[21];
                    //  for a blank megatile with a totaly flat area
                    //  all height fields have the same sealevel value of:
                    //  Region         Seattle aaaj   Sanfran blank
                    //  const. value   0x01B1 433     0x439  classification is water
                    //  min height     0x01AE 430     0x436
                    //  max height     0x01B4 436     0x43C
                    //  Sealevel       -1 ft          270 ft
        
Size in
bytes





34



24



















30

















162






680
Level 2 Geos - 16 subfiles per megatile

Size: 896 data bytes per subfile
Total size with the header: 16*896=14336+34=14370 bytes


Name padded with 0x00
l2blank.geo : level 2 for the blank megatile
l2aaaa.geo  : level 2 for megatile aaaa

TileInfo from this point the data structure will repeat 16 times dividing the megatile in 4 x 4 subsquares
  typedef struct _TileInfo
  {
    WORD *hfield;  // pointer to height field, unused addr.
                   //   Region   Seattle       Sanfran
                   //   pointer  0x00E285F2    0x00000000
    long row;      // row posts for hfield = 9
    long xdel;     // startpoint x inside region for the 16 subfiles
                   //   aiah ai is tile 9 xdel=(9-1)*4 + m_lsx[i]= 32 + m_lsx[i]
                   //   m_lsx[16]={0,0,1,1,2,2,3,3,0,0,1,1,2,2,3,3};
                   //   negative for blank tiles xdel=m_blsx[i]
                   //   m_blsx[16]={-4,-4,-3,-3,-2,-2,-1,-1,-4,-4,-3,-3,-2,-2,-1,-1};
    long ydel;     // startpoint y inside region for the 16 subfiles
                   //   aiah, ah is tile 8, ydel=(8-1)*4 + m_lsy[i]=28 + m_lsy[i]
                   //   m_lsy[16]={0,1,0,1,0,1,0,1,2,3,2,3,2,3,2,3};
                   //   negative for blank tiles ydel=m_blsy[i]
                   //   m_blsy[16]={-4,-3,-4,-3,-4,-3,-4,-3,-2,-1,-2,-1,-2,-1,-2,-1};
    long lod;      // base level of detail = 2
    long num_levs; // number of levs = 5
  } TileInfo;
        
QuadLevel (5 entries at 6 bytes)
  typedef struct _QuadEntry
  {
    QuadInfo *quads; // pointer to quad array, unused addr.
    WORD post;       // post separation
  } QuadLevel[5];

        Region   Seattle       Sanfran
        pointer  0x00E28694    0x00000000
        posts    8             8
        pointer  0x00E2869C    0x00000000
        posts    4             4
        pointer  0x00E286BC    0x00000000
        posts    2             2
        pointer  0x00E2873C    0x00000000
        posts    1             1
        pointer  0x00000000    0x00000000
        posts    0             0
        
height fields (9 * 9 entries at 2 bytes)
  WORD heightfld[81];  // (height in ft + region sealevel) * 4 + classification bits
                       //    classification bits: 0 land, 1 water, 2 urban
                       //  1 ft = 0.3048 m  and  1 m = 3.28 ft
        
QuadInfos (1+4+16+64 = 85 entries at 8 bytes)
  typedef struct _QuadInfo
  {
    float inv_err2; // max lod height error squared and inverted ?!?!?!
                    //    for a totaly flat area the value = 1.00000e+012
                    //    The lowest bit of the QuadInfo inv_err2 field is the
                    //    "split direction" bit which determines if the quad is split
                    //    into triangles topleft-bottomright(\) or topright-bottomleft(/)
    WORD min;       // min height for boundingbox, for the blank flat area: heightsfld-3
    WORD max;       // max height for boundingbox, for the blank flat area: heightsfld+3
  } QuadInfo[85];
        
Size in
bytes





34



24











































24
















162





168
Level 5 Geos - 1024 subfiles per megatile

Size: 378 data bytes per subfile
Total size with the header: 1024*378=387072+34=387106 bytes


Name padded with 0x00
l5blank.geo : level 5 for the blank megatile
l5aaaa.geo  : level 5 for megatile aaaa

TileInfo from this point the data structure will repeat 1024 times dividing the megatile in 32 x 32 subsquares
  typedef struct _TileInfo
  {
    WORD *hfield;  // pointer to height field, unused addr.
                   //   Region   Seattle       Sanfran
                   //   pointer  0x00EB1550    0x00000000
    long row;      // row posts for hfield = 9
    long xdel;     // startpoint x inside region for the 1024 subfiles
                   //   aiah ai is tile 9 xdel=(9-1)*32 + m_hsx[i]= 256 + m_hsx[i]
                   //   m_hsx[1024]=4*{4*{ 0, 0, 1, 1, 2, 2, 3, 3},
                   //                  4*{ 4, 4, 5, 5, 6, 6, 7, 7},
                   //                  4*{ 8, 8, 9, 9,10,10,11,11},
                   //                  4*{12,12,13,13,14,14,15,15} },
                   //               4*{4*{16,16,17,17,18,18,19,19},
                   //                  4*{20,20,21,21,22,22,23,23},
                   //                  4*{24,24,25,25,26,26,27,27},
                   //                  4*{28,28,29,29,30,30,31,31} };
                   //   negative for blank tiles xdel=m_bhsx[i]
                   //   m_bhsx[1024]=4*{4*{-32,-32,-31,-31,-30,-30,-29,-29},
                   //                   4*{-28,-28,-27,-27,-26,-26,-25,-25},
                   //                   4*{-24,-24,-23,-23,-22,-22,-21,-21},
                   //                   4*{-20,-20,-19,-19,-18,-18,-17,-17} },
                   //                4*{4*{-16,-16,-15,-15,-14,-14,-13,-13},
                   //                   4*{-12,-12,-11,-11,-10,-10,- 9,- 9},
                   //                   4*{- 8,- 8,- 7,- 7,- 6,- 6,- 5,- 5},
                   //                   4*{- 4,- 4,- 3,- 3,- 2,- 2,- 1,- 1} };
    long ydel;     // startpoint y inside region for the 1024 subfiles
                   //   aiah, ah is tile 8, ydel=(8-1)*32 + m_hsy[i]=224 + m_hsy[i]
                   //   m_hsy[1024]=4*{4*{ 0, 1},4*{ 2, 3},4*{ 4, 5},4*{ 6, 7}},
                   //               4*{4*{ 8, 9},4*{10,11},4*{12,13},4*{14,15}},
                   //               4*{4*{16,17},4*{18,19},4*{20,21},4*{22,23}},
                   //               4*{4*{24,25},4*{26,27},4*{28,29},4*{30,31}},
                   //               4*{4*{ 0, 1},4*{ 2, 3},4*{ 4, 5},4*{ 6, 7}},
                   //               4*{4*{ 8, 9},4*{10,11},4*{12,13},4*{14,15}},
                   //               4*{4*{16,17},4*{18,19},4*{20,21},4*{22,23}},
                   //               4*{4*{24,25},4*{26,27},4*{28,29},4*{30,31}};
                   //   negative for blank tiles ydel=m_bhsy[i]
                   //   m_bhsy[1024]=4*{4*{-32,-31},4*{-30,-29},4*{-28,-27},4*{-26,-25}},
                   //                4*{4*{-24,-23},4*{-22,-21},4*{-20,-19},4*{-18,-17}},
                   //                4*{4*{-16,-15},4*{-14,-13},4*{-12,-11},4*{-10,- 9}},
                   //                4*{4*{- 8,- 7},4*{- 6,- 5},4*{- 4,- 3},4*{- 2,- 1}},
                   //                4*{4*{-32,-31},4*{-30,-29},4*{-28,-27},4*{-26,-25}},
                   //                4*{4*{-24,-23},4*{-22,-21},4*{-20,-19},4*{-18,-17}},
                   //                4*{4*{-16,-15},4*{-14,-13},4*{-12,-11},4*{-10,- 9}},
                   //                4*{4*{- 8,- 7},4*{- 6,- 5},4*{- 4,- 3},4*{- 2,- 1}};
    long lod;      // base level of detail = 5
    long num_levs; // number of levs = 4
  } TileInfo;
        
QuadLevel (4 entries at 6 bytes)
  typedef struct _QuadEntry
  {
    QuadInfo *quads; // pointer to quad array, unused addr.
    WORD post;       // post separation
  } QuadLevel[4];

        Region   Seattle       Sanfran
        pointer  0x00EB15F2    0x00000000
        posts    8             8
        pointer  0x00EB15FA    0x00000000
        posts    4             4
        pointer  0x00EB161A    0x00000000
        posts    2             2
        pointer  0x00000000    0x00000000
        posts    1             1
        
height fields (9 * 9 entries at 2 bytes)
  WORD heightfld[81];  // (height in ft + region sealevel) * 4 + classification bits
                       //    classification bits: 0 land, 1 water, 2 urban
                       //  1 ft = 0.3048 m  and  1 m = 3.28 ft
        
QuadInfos (1+4+16 = 21 entries at 8 bytes)
  typedef struct _QuadInfo
  {
    float inv_err2; // max lod height error squared and inverted ?!?!?!
                    //    for a totaly flat area the value = 1.00000e+012
                    //    The lowest bit of the QuadInfo inv_err2 field is the
                    //    "split direction" bit which determines if the quad is split
                    //    into triangles topleft-bottomright(\) or topright-bottomleft(/)
    WORD min;       // min height for boundingbox, for the blank flat area: heightsfld-3
    WORD max;       // max height for boundingbox, for the blank flat area: heightsfld+3
  } QuadInfo[21];
        
Start of
Geos-Table



Geos-Table: 3 entries at 30 bytes per megatile
    int  number of entries  // 3 * megatiles defined in this file
  struct GEO_TABENTRY
  {                         // GeoTiles table
    BYTE Tilename[22];      // Tilename Lnaxay.geo, padded with 0x00
    int  Offset;            // Offset in GeoTiles.tag file
    int  Size;              // Tilesize level0=266, level2=14336, level5=387072
  } m_GeoTabEntry[3*megatiles];
        

under construction

Information: André Meystre
back to FU3 Terrain Workshop      next to Using the GeoTilesViewer tool