Starcraft 2 Model Format Pt. 2

Due to popular request, here is the second installment of my Starcraft 2 .m3 model format series.
First of all, I’ve gotten quite a few mails and comments on this topic. This includes some links to other people also working on the format, so here is a collection of links:
Thanks everyone for the links!
Today I’ll chat a little about the stored meshes. There are threed parts to rendering out simple meshes: the vertices, the faces, and the regions. But first, we need to find them.
That’s where the MODL chunk comes in. It should be considered the header of the m3 format. It defines where to find what. However, we’re only interested in a few parts. It’s important to note that I’ve found two different versions of the MODL header, version 0x14 and 0x17. 0x14 is slightly different, and I’m not going to talk about it in this post.
struct MODLHeader {
uint32 stuff_we_dont_need_atm[17]
uint32 flags
uint32 vertex_data_size
uint32 vertex_tag
uint32 div_count
uint32 div_tag
[… some more data we don’t care about…]
The precious parts here are the reference to the vertices, the reference to the div, and the flags. The flags will become handy when parsing the vertices, as they describe what can be found in the vertex format. In detail:
if flags & 0x40000 == 0:
vertex_size = 32
vertex_size = 36
There are def. other information in the flags, but this is what we need right now. The vertices can now be found at the vertex tag, which is just a collection of bytes. The number of vertices is vertex_data_size / vertex_size. The format is as follows:
struct Vertex {
float32 position[3]
uint8 boneweights[4]
uint8 boneindices[4]
uint8 normal[4]
uint16 uv[2]
if vertex_size == 36: {
uint8 unknown[4]
uint8 tangent[4]
Position is just the object space position of the vertex.
Boneweights range from 0 to 255 and represent the weight factor (divided by the sum of all weights) for each bone matrix.
Boneindices are indices that point to the corresponding bone matrix.
The normal is compressed and can be extracted as c = 2*c/255.0-1 for each component.
The uvs are scaled by 2048, so they need to be divided by 2048 to be used in opengl.
Finally, the tangent is compressed in the same way as the normal.
To get the bitangent and set up a correctly oriented and orthonormal tangent space, we need to take the cross product of the normal and tangent, and then multiply it by the w component of the normal: ( cross*n.w
The DIV is really just a container for two other important chunks, the faces and the regions:
struct DIV {
uint32 indices_count
uint32 indices_tag
uint32 regions_count
uint32 regions_tag
[… some other stuff …]
The triangles are just stored as tripplets in the indices U16_ tag. There are indices_count/3 triangles in the mesh.
To correctly render the mesh, we also need the region chunk:
struct Region {
uint32 unknown — Note: updated, thanks to NiNtoxicated!
uint16 vertex_offset
uint16 vertex_count
uint32 index_offset
uint32 index_count
uint8 unknown[12]
Now, to render a mesh, we can iterate through all regions, and for each region, we render index_count/3 triangles with the indices from the indices chunk. This looks something like this:
def drawModel(self):
for region in div.regions:
for i in range(region.index_count):
v = vertices[div.indices[region.index_offset+i]]
GL.glTexCoord(v.uv[0]/2048.0, v.uv[1]/2048.0)
GL.glVertex(v.position[0], v.position[1], v.position[2])
And that’s it! Setting up the proper textures and materials is rather complex and is def. worth another blog entry 😛 That’s it for now. As usual, let me know if you have any questions or comments!
PS: let me know if you know how to properly format source code on blogger 🙂

3 thoughts on “Starcraft 2 Model Format Pt. 2

  1. You can check out the php obj exporter scripts at the links i posted. you can then import the obj in maya.

Leave a Reply

Your email address will not be published. Required fields are marked *