Skinning Data Section

This section describes how bones influence the data of skinned meshes. Skinned position/normal data in the mesh section gets updated at runtime based on this section.

Header

Offset Type Description
0x0 uint16 Number of SK1 headers
0x2 uint16 Number of SK2 headers
0x4 uint16 Number of SKAcc headers
0x6 uint8 Quantize info (This seems to only work correctly when the high nibble is 0)
0x7 byte Padding
0x8 uint32 Pointer to SK1 structures (relative to header)
0xc uint32 Pointer to SK2 structures (relative to header)
0x10 uint32 Pointer to SKAcc structures (relative to header)
0x14 uint32 Pointer to top of memory to clear in the mesh data section (relative to... something near the start of that section. I haven't pinned down exactly where.)
0x18 uint32 Size of memory to clear in the mesh data
0x1c uint32 Pointer to array of vertex position indices (?) to 'flush' (I don't know what this does)
0x20 uint32 Number of indices to flush

The memory clearing part is weird. The SKAcc headers work by adding (accumulating) the influences from multiple bones in-place in the position data, so without clearing the memory the correct value for a skinned position will be added to whatever position was used last frame.

SK1

The simplest case. This signifies a group of vertex positions that are only influenced by a single bone.

Offset Type Description
0x0 Matrix An 0x30 byte placeholder for a matrix that gets populated at runtime based on the SK1's bone's animation. I believe this is a 4x3 matrix, with the last column assumed to be (0, 0, 0, 1).
0x30 uint32 Source position/normal array pointer (relative to header)
0x34 uint32 Destination position/normal pointer
0x38 uint16 Bone index (which bone influences these vertices)
0x3a uint16 Vertex count
0x3c uint8 Position/normal offset (I don't know why this exists instead of already being added to both pointers, probably some performance reason)
0x3d byte[3] Padding

The way this works is pretty weird.


First, read the quantized data from (source_position_normal_array_pointer + position_normal_offset). This holds a series of alternating positions and normals, one of each per vertex, so will be (3 + 3) * vertex_count items long. Skinned meshes store six values per position, an x, y, z for the position followed by an x, y, z for the normal. This might mean that the normal section in a skinned mesh's data is completely ignored?


After being transformed, the position/normal data from the source is inserted into the position data of the first mesh at (destination_position_normal_pointer + position_normal_offset) relative to the start of the raw quantized position data. Effectively, there are vertex_cnt sequential vertex positions influenced by this SK1's bone with an assumed weight of 255, starting at index (destination_position_normal_array_pointer + position_normal_offset) / (quantized_data_size(mesh_data.meshes[0].positions.quantization) * 6)

SK2

Very similar to SK1, but there are two bones influencing each vertex with different weights.

Offset Type Description
0x0 Matrix An 0x60 byte placeholder for a two matrices, one per bone
0x30 uint32 Source position/normal array pointer (relative to header)
0x34 uint32 Weight array pointer (relative to header)
0x38 uint32 Destination position/normal pointer
0x3c uint16 Bone index 1
0x3e uint16 Bone index 2
0x40 uint16 Vertex count
0x42 uint8 Position/normal offset
0x43 byte Padding

Process this similar to SK1 entries. The only difference is that there's now a weight array (don't add the position/normal offset to the weight array pointer) that has vertex_count * 2 entries, each a uint8 between 1 and 255. Bone 1 influences vertex n with a weight of weights[n * 2], and bone 2 influences vertex n with a weight of weights[n * 2 + 1]. The weights on each vertex should sum to 255 once all the SK entries have been processed.

SKAcc

These entries are used to supplement SK2s for vertices that have more than two bone influences.

Offset Type Description
0x0 Matrix An 0x30 byte placeholder for a matrix
0x30 uint32 Source position/normal array pointer (relative to header)
0x34 uint32 Destination position/normal index array pointer (relative to header)
0x38 uint32 Destination position/normal pointer
0x3c uint32 Weight array pointer (relative to header)
0x40 uint16 Bone index (which bone influences these vertices)
0x42 uint16 Vertex count

This entry type has one bone index and one weight per vertex. Its gimmick is that it has an array of destination indexes stored as uint16s, rather than a single pointer in the position data to place the entries into sequentially. Each vertex position's index is (destination_position_normal_array_pointer + position_normal_offset) / (quantized_data_size(mesh_data.meshes[0].positions.quantization) * 6) + destination_position_normal_index_array[n]