This section holds position, normal, color, and uv mapping information for the vertices, as well as draw lists describing the triangles created with the vertices.
Offset | Type | Description |
---|---|---|
0x0 | Magic (0x00B749E0 for Sluggers) | |
0x4 | uint32 | User data length |
0x8 | uint32 | User data pointer (relative to header) |
0xc | uint32 | Number of meshes |
0x10 | uint32 | Pointer to mesh header array (relative to header) |
Offset | Type | Description |
---|---|---|
0x0 | uint32 | Mesh data header pointer (relative to header) |
0x4 | char* | Mesh name pointer (relative to header) |
Offset | Type | Description |
---|---|---|
0x0 | uint32 | Position data pointer (relative to mesh data header) |
0x4 | uint32 | Color data pointer (relative to mesh data header) |
0x8 | uint32 | UV mapping data pointer (relative to mesh data header) |
0xc | uint32 | Normal data pointer (relative to mesh data header) |
0x10 | uint32 | Draw list pointer (relative to mesh data header) |
0x14 | uint8 | Number of texture channels |
0x15 | byte[3] | Padding |
Once you analyze the position, color, uv mapping, and normal data referenced by this header, you'll have an array for each of the four data types that the draw list will read from.
Offset | Type | Description |
---|---|---|
0x0 | uint32 | Quantized raw position data ptr (relative to mesh data header) |
0x4 | uint16 | Number of positions |
0x6 | uint8 | Quantization |
0x7 | uint8 | Component count (usually 3, 6 if the mesh is skinned and includes alternating position and normal data) |
Offset | Type | Description |
---|---|---|
0x0 | uint32 | Quantized raw color data ptr (relative to mesh data header) |
0x4 | uint16 | Number of colors |
0x6 | uint8 | Quantization |
0x7 | uint8 | Component count |
Offset | Type | Description |
---|---|---|
0x0 | uint32 | Quantized raw uv (technically st) coord data ptr (relative to mesh data header) |
0x4 | uint16 | Number of coords |
0x6 | uint8 | Quantization |
0x7 | uint8 | Component count |
0x8 | char* | Palette name pointer (relative to mesh data header) |
0xc | uint32 | Palette pointer (relative to mesh data header) |
Offset | Type | Description |
---|---|---|
0x0 | uint32 | Quantized raw normal data ptr (relative to mesh data header) |
0x4 | uint16 | Number of normals |
0x6 | uint8 | Quantization |
0x7 | uint8 | Component count |
0x8 | float | Ambient percentage (?) |
Offset | Type | Description |
---|---|---|
0x0 | uint32 | Primitive data pointer (not used directly) |
0x4 | uint32 | Entry array pointer (relative to mesh data header) |
0x8 | uint16 | Number of entries in draw list |
0xa | byte[2] | Padding |
Once you've analyzed the other data sections, analyze the draw list entry by entry.
Offset | Type | Description |
---|---|---|
0x0 | uint8 | Entry type |
0x1 | byte[3] | Padding |
0x4 | bitmap32 | Setting |
0x8 | uint32 | Primitive list pointer (relative to the mesh data header) (might be null) |
0xc | uint32 | Primitive list size |
Each time you analyze a draw list entry, do two things: update the draw state, and draw the triangles specified by the entry's primitive list if one exists.
Each triangle drawn by the primitive list has three vertices, each with a combination of a position, a normal, up to two colors, and up to eight texture coordinates. The draw state specifies which of those components are in use and how data will be selected for each component.
How the draw state is changed depends on the entry type. The important entries I've run into are
This type specifies which components are provided to the primitive list for each vertex and how the data is chosen for each. The psuedocode is
state.expected_components = []
components = ['position', 'normal', 'color0', 'color1', 'texture0', 'texture1', 'texture2', 'texture3', 'texture4', 'texture5', 'texture6', 'texture7']
// the first couple bytes have to do with something called the 'position matrix index', I just ignore them
setting = setting << 2
for component in components:
// the two bits in the setting describing this component
this_setting = setting & 0b11
enabled = this_setting != 0
// if this is set, the value is provided in the primitive list instead of an index into one of the data arrays in the mesh data
// I haven't seen this used
immediate = setting == 0b01
indexed = !immediate
index_size = 1 + (setting & 0b01)
if enabled:
expected_components.append({'name': component, 'immediate': immediate, 'index_size': index_size})
setting = setting << 2
You may end up with a list like
[{'name': 'position', 'immediate': 0, 'index_size': 2},
{'name': 'normal', 'immediate': 0, 'index_size': 2},
{'name': 'color0', 'immediate': 0, 'index_size': 1},
{'name': 'texture0', 'immediate': 0, 'index_size': 2}
{'name': 'texture1', 'immediate': 0, 'index_size': 2}]
meaning that a primitive list at this point would expect 9 bytes per vertex: a two-byte index into the position list, a two-byte index into the normal list, a one-byte index into the vertex colors list, a two-byte index into the texture coordinates list to get a coordinate for texture layer 0, and another two-byte index to get a coordinate for texture layer 1, in that order.
This type of entry is simpler, it just describes which texture entry from the texture section of the file is used by a layer. Pseudocode:
texture_index = setting & 0b0001111111111111
setting = setting >> 13
layer = setting & 0b111
setting = setting >> 3
wraps = setting & 0b1111
setting = setting >> 4
wrapt = setting & 0b1111
state.texture_layers[layer] = {'index': texture_index, 'wraps': wraps, 'wrapt': wrapt}
so a setting of 0b00000000 00000000 001 0000000000101 would set texture layer 1 to whatever the 6th texture in the texture section is. wraps and wrapt specify what happens when the s and t (x and y) coordinates extend past the edge of an image, but I just assume the default behavior of whatever modeling software I'm using which is usually fine.
Offset | Type | Description |
---|---|---|
0x0 | uint8 | Primitive Type (I believe 0x90 is triangles, 0x98 is triangle strips, 0x80 is quads) |
0x1 | uint16 | Vertex count |
0x3 | binary | Vertex data (follows the format set by entry type 3) |
Once you've parsed that, you'll have a bunch of vertices, each looking something like (assuming immediate positioning isn't used){'position': positions[378],
'normal': normals[350],
'color0': colors[5],
'texture0': texture_coords[254],
'texture1': texture_coords[875]}
Now you draw triangular faces from those vertices. The code from my awful python parser looks like:
match primitive:
case 0x90:
# TRIANGLES
faces += [vertexes[x:x+3] for x in range(0, len(vertexes), 3)]
case 0x98:
# TRIANGLESTRIP
order = 1
for i in range(0, len(vertexes) - 2, 1):
if order:
faces.append([vertexes[i], vertexes[i + 1], vertexes[i + 2]])
else:
faces.append([vertexes[i], vertexes[i + 2], vertexes[i + 1]])
order = not order
case 0x80:
# QUADS
for x in range(0, len(vertexes), 4):
faces.append([vertexes[x], vertexes[x+1], vertexes[x+2]])
faces.append([vertexes[x], vertexes[x+2], vertexes[x+3]])
case _:
print ("primitive type " + hex(primitive) + " not supported")
If you haven't parsed the entire primitive list by now, repeat the entire process. The list may draw a different type of primitive as well.