Packet

TRN.ASWM packets are used to store the walkmesh information in TRN and TRX files.

The packet itself is stored as is:

TypeNameDescription
char[4]data_typeAlways COMP for compressed walkmesh
uint32_tcompressed_data_sizeSize of compressed_data
uint32_tuncomp_lengthUncompressed walkmesh size
void[compressed_data_size]compressed_datazlib-compressed walkmesh data

Uncompressed walkmesh data can be retrieved using zlib.deflate. The following structure information are for the uncompressed walkmesh data.

TRN/TRX differences

TRN.ASWM is slightly different weather it is stored in a TRX or TRN file. Generally, the TRX version is smaller as it has to be downloaded/loaded by the client, and contains the baked version of the walkmesh (with placeable walkmesh/walkmesh cutter alterations).

  • TRN version:
    • The whole map is stored, but no information on placeable walkmeshes nor walkmesh cutters
    • Triangle clockwise flag is not set
    • Contains map border geometry
    • Path tables & island list are empty
  • TRX version:
    • Baked counterpart of the TRN version
    • Map borders are not stored, only the center.
    • Triangles cut by a "walkmesh cutter" are removed (creates holes in the mesh)
    • Placeable walkmesh modifications appears
    • Path tables & island list are populated

Data blocks

TypeNameDescription
Headerheader
Vertex[header.vertices_count]vertices
Edge[header.edges_count]edges
Triangle[header.triangles_count]triangles

TilesHeader

tiles_header

Tile[tiles_header.grid_height * tiles_header.grid_width]

tiles
uint32_ttiles_border_sizeWidth of the map borders in tiles
uint32_tislands_lengthLength of islands
Island[islands_length]islands
IslandPathNode[islands_length * islands_length]islands_path_nodesPathfinding data

Header struct (53 bytes)

TypeNameDescription
uint32_tversion
char[32]name
uint8_towns_data
uint32_tvertices_count
uint32_tedges_count
uint32_ttriangles_count
uint32_ttriangles_offsetSeems to be always 0

Vertex struct (12 bytes)

TypeDescription
float[3]x/y/z coordinates of the vertex

Edge struct (16 bytes)

TypeDescription
uint32_t[2]Vertex indices
uint32_t[2]Attached triangle indices. -1 if a triangle does not exist.
Notes
  • The struct defines a edge between two triangles, with the shared edge.
  • Every triangle has 3 Edge structs.
  • Probably only used to ease path tables generation
  • TODO: I should try remove all Edges from a baked trx and see what happens

Triangle struct (64 bytes)

TypeNameDescription
uint32_t[3]verticesVertex indices composing the triangle
uint32_t[3]linked_edgesEdge indices corresponding to this triangle edges
uint32_t[3]linked_trianglesTriangle indices that have an edge in common with this triangle. -1 is there is no triangle on one.
float[2]centerTriangle center x/y coordinates
float[3]normalNormal vector
floatdotDot product at plane
uint16_tisland-1 if the triangle is non walkable, else it is an island index
uint16_tflagsWalkmesh flags
  • linked_triangles[i] and linked_edges[i] must share the same edge
Triangle flags
  • walkable = 0x01: if the triangle can be walked on
  • clockwise = 0x04: vertices are wound clockwise and not ccw
  • dirt = 0x08: Floor type (for sound effects)
  • grass = 0x10: Floor type (for sound effects)
  • stone = 0x20: Floor type (for sound effects)
  • wood = 0x40: Floor type (for sound effects)
  • carpet = 0x80: Floor type (for sound effects)
  • metal = 0x100: Floor type (for sound effects)
  • swamp = 0x200: Floor type (for sound effects)
  • mud = 0x400: Floor type (for sound effects)
  • leaves = 0x800: Floor type (for sound effects)
  • water = 0x1000: Floor type (for sound effects)
  • puddles = 0x2000: Floor type (for sound effects)
island & walkable flag
  • In TRN files:
    • Map border megatiles triangles have island=-1 and walkable flag set to 0
    • Bakeable triangles (map center) have a valid island (ie: != -1), and the walkable flag set to 1/0 if the triangle is meant to be walkable/non walkable.
  • In TRX files:
    • Map borders are not stored
    • Non walkable triangles have island=-1 and walkable flag set to 0
    • Triangles cut by a "walkmesh cutter" are not stored
Notes
  • For the raw map:
    • unknownA is always 0 (or -0)
  • For the icy peak map
    • -498.294 ==> 475.229

TilesHeader struct (20 bytes)

TypeNameDescription
uint32_tflagsAlways 31 in TRX files, 15 in TRN files
floatwidthWidth in meters of a terrain tile (most likely to be 10.0)
uint32_tgrid_heightNumber of tiles along Y axis
uint32_tgrid_widthNumber of tiles along X axis
uint32_tborder_sizeWidth of the map borders in tiles (8 means that 8 tiles will be removed on each side)

Tile struct (variable length)

A tile is a square (generally 10 x 10 meters) containing mesh triangles. In TRX files, the tiles also contains pre-calculated pathfinding information.

TypeNameDescription
TileHeaderheader
Vertex[]verticesUnused by nwn2. Might be usable with tile.header.owns_data == true
Edge[]edgesUnused by nwn2. Might be usable with tile.header.owns_data == true
PathTableHeaderpath_table_header
ubyte[]local_to_nodelocal_to_node[localTriangleIndex] is an index in nodes
uint32_t[]node_to_localnode_to_local[nodeValue] is a local triangle index
ubyte[]nodesnodes[node_to_local_length * fromLTNIndex + destLTNIndex] is an index in node_to_local
uint32_tflagsReuse value aswm.tiles_header.tiles_flags
  • All triangles of a tile must have consecutive indices
  • The triangle owned by a tile can be retrieved using aswm.triangles[header.triangles_offset .. header.triangles_offset + header.triangles_count]
  • A tile must not re-use triangles owned by another tile (will crash NWN2)
  • TODO: I need to try making non-square tiles and see how the game handles it

Tile path table

This is a pre-calculated pathfinding table, that reference the next triangle to go to in order to reach any other triangle of the tile.

This is how a path on a tile is calculated:

  1. Get local triangle indices:
    • fromLocIdx = fromGlobIdx - tile.header.triangles_offset for the starting triangle
    • destLocIdx = destGlobIdx - tile.header.triangles_offset for the triangle you are trying to reach
    • If fromLocIdx or destLocIdx equals 255, it means the triangle is not walkable
  2. Get node indices:
    • fromNodeIdx = tile.local_to_node[fromLocIdx]
    • destNodeIdx = tile.local_to_node[destLocIdx]
  3. Get the Node value
    • node = tile.nodes[fromNodeIdx * header.node_to_local_length + destNodeIdx]
    • node == 255 can mean two things:
      • Destination cannot be reached, because one of the triangles is non- walkable or there is an obstacle that can't be get around without leaving the tile.
      • If fromNodeIdx == destNodeIdx, this means you already reached the destination triangle
  4. Get the next local triangle index to go to in order to reach destination
    • nextLocIdx = tile.node_to_local[node & 0b0111_1111]
  5. Loop on 2. with fromNodeIdx = nextLocIdx, until nextLocIdx == destNodeIdx

Notes:
  • nodes[i] & 0b1000_0000 is a bit flag for if there is a clear line of sight between the two triangle. It's not clear what LOS is since two linked triangles on flat ground may not have LOS = 1 in game files. A value of 0 means the game will have to calculate LOS in real time.

TileHeader struct (57 bytes)

TypeNameDescription
char[32]nameSometime contains garbage, sometime contains only null values
ubyteowns_data1 if the tile stores vertices / edges. NWN2 always set this to 0
uint32_tvertices_countNumber of vertices in this tile
uint32_tedges_countNumber of edges in this tile
uint32_ttriangles_countNumber of triangles in this tile (walkable + unwalkable)
floatsize_xTODO: Always 0?
floatsize_yTODO: Always 0?
uint32_ttriangles_offset

PathTableHeader struct (13 bytes)

TypeNameDescription
uint32_tcompression_flagsAlways 0. Note: it's pointless to compress path table since the aswm is already compressed
uint32_tlocal_to_node_lengthLength of local_to_node array
ubytenode_to_local_lengthLength of node_to_local array
uint32_trle_table_sizeAlways 0.
  • compression_flags values: rle = 1, zcompress = 2

Island struct (variable length)

An island is a fraction of a tile, where all walkable triangles can be reached without leaving the tile. This is a pathfinding related struct that is not available in TRN files.

TypeNameDescription
IslandHeaderheader
uint32_tlinked_islands_lengthLength of linked_islands
uint32_t[]linked_islandsIsland indices linked to this one
uint32_tlinked_islands_dist_lengthLength of linked_islands_dist
float[]linked_islands_distDistance to linked islands
uint32_tlinked_islands_exit_lengthLength of linked_islands_exit
uint32_t[]linked_islands_exitExit triangles leading to linked islands
  • linked_islands_dist[i] and linked_islands_exit[i] are the distance & exit to linked_islands[i]
  • I don't see the point of storing 3 times the same length. Maybe the nwn2 devs did not have time clean this.

IslandHeader struct (24 bytes)

TypeNameDescription
uint32_tindexIndex of the island in the aswm.islands array
uint32_ttileValue looks pretty random, but is identical for all islands. TODO: try setting it to associated tile index
float[3]centerCenter of the island. Z is always 0
uint32_ttriangles_countNumber of triangles in this island

IslandPathNode struct (8 bytes)

Pathfinding across islands works very similarly to Tile path table.

aswm.islands_path_nodes[fromIslandIdx * aswm.islands_length + toIslandIdx] is the next island to go to in order to reach toIslandIdx from fromIslandIdx

TypeNameDescription
uint16_tnextNext island to go to
uint16_t_paddingunused
floatweightDistance to the next island
  • No labels