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 DataEdit
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 leftmost column likewise. The bottom row is a copy of the cell to the south's uppermost row. Likewise the leftmost column is a copy of the cell to the west's rightmost data column.
Name | Type | Info |
---|---|---|
Offset | 32-bit float | The height offset for this entire cell; specifies the height of the most southwestern 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 leftmost column likewise. The bottom row is a copy of the cell to the south's uppermost row. Likewise the leftmost column is an copy of the cell to the west's rightmost 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 following 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; }