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.
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.
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)
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.
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]