Tes5Mod:Mod File Format/LAND

The UESPWiki – Your source for The Elder Scrolls since 1995
Jump to: navigation, search


C V Field Name Type/Size Info
* ATXT Additional texture formid References land texture records except when the reference is zero.
uint8 Specifies the quadrant this ATXT record applies to. 0 = bottom left. 1 = bottom right. 2 = upper-left. 3 = upper-right (the same as Oblivion).
1 byte Unknown.
uint16 The texture layer, between 0 and 7 (i.e. 8 layers). The CS only allows up to 9 layers; the BTXT takes up the bottom layer. Uncertain of the game engine's limit (the same as Oblivion).
* BTXT Base texture formid References land texture records except when the reference is zero. If the reference is zero the engine falls back to "dirt02.dds" as standard ground-layer.
uint8 Specifies the quadrant this BTXT record applies to. 0 = bottom left. 1 = bottom right. 2 = upper-left. 3 = upper-right. (the same as Oblivion).
3 bytes Unknown.
 ? DATA 4 bytes
 ? VCLR Vertex color 3267 bytes data (the same as Oblivion).
 ? VHGT Vertex height 1096 bytes data (the same as Oblivion).
 ? VNML vertex normals 3267 bytes 33x33 grid of vertex normals, with 3 bytes per vertex for the X, Y, Z normals (the same as Oblivion). The first row and column overlaps with the north and west cell.
* VTXT 8 bytes* A variable length list of 8 byte records

Vertex Height Data[edit]

The VHGT record is a 3D mesh that has been highly optimized for storing landscape data.

Each data-point is a signed byte. Data starts from the bottom left cell position and is filled one row at a time. Each value of 1 equates to a height of 8 game units (assumed from Oblivion) so each data point has an incremental game unit range of -1024 to +1016. Each value of the leftmost column offsets the height of all vertex data at that row and to the north. All grid-points of the remaining 32x32 grid offset the height of all landscape at that point and all points to the east just on that row.

The height values for the four edges of the mesh must match the height values of the matching edge of neighboring landscape meshes. The bottom row values should join with the cell to its south. The left-most column likewise. The bottom row is a copy of the cell to the south's upper-most row. Likewise the left-most column is an copy of the cell to the west's right-most data column.

Name Type Info
Offset 32-bit float The height offset for this entire cell; specifies the height of the most south-western point. Each value of 1 equates to a height of 8 game units.
Gradient data 1089 bytes 33x33 grid of height data (1089 bytes). Each data-point is a signed byte. Data starts from the bottom left cell position and is filled one row at a time. Each value of 1 equates to a height of 8 game units so each data point has an incremental game unit range of -1024 to +1016. The bottom row values should join with the cell to its south. The left-most column likewise. The bottom row is a copy of the cell to the south's upper-most row. Likewise the left-most column is an copy of the cell to the west's right-most data column. Each value of the leftmost column offsets the height of all vertex data at that row and to the north. All grid-points of the remaining 32x32 grid offset the height of all landscape at that point and all points to the east just on that row.
Unknown 3 bytes Unknown. Haven't noticed any ill-effects just filling this with arbitrary values in TES3 or TES4. This is probably just a 3-byte filler so that the entire subrecord's data can be aligned on a 4 byte word boundary.

The followng code snippet explains how to interpret VHGT fields:


float[33,33]   heightmap; // heightmap in game units
            

float offset = ReadFloat() * 8; // whole cell offset each unit equals 8 game units
float row_offset = 0;

for (int i = 0; i < 1089; i++)
{
        float value = ReadSByte()*8; // signed byte, each unit equals 8 game units
        
        int r = i / 33;
        int c = i % 33;

        if (c == 0) // first column value controls height for all remaining points in cell 
        {
           row_offset = 0;
           offset += value;
        }

        else // other col values control height of all points in the same row
        {
          row_offset += value;
        }

         heightmap[c, r] = offset + row_offset;

}