C sharp:MD2 loader in CSharp

From GPWiki

This little tutorial is supposed to give you an idea on how to load MD2 data using C#. This doesn't cover rendering or complex data structures to hold the data (yet!), it's just my method of loading data from MD2 files into List structures (provided by C#). You should know from the start that the code isn't optimal or perfect since I wrote this while learning C#. I also wont explain the C# techniques I used since this is not a C# tutorial nor will I explain the MD2 format, there are plenty tutorials available about this subject (you should probably study one of those tutorials to have a clue about how a MD2 file is organized). So enjoy and happy coding.

So here we go, the method I used was to create some structs in which I will read the different data blocks in a MD2 model.

struct md2_header
    {
        public int ident;              // signature. must be equal to "IPD2"
        public int version;            // md2 version. must be equal to 8
 
        public int skinwidth;          // width of the texture
        public int skinheight;         // height of the texture
        public int framesize;          // size of one frame in bytes
 
        public int num_skins;          // number of textures
        public int num_xyz;            // number of vertices
        public int num_st;             // number of texture coordinates
        public int num_tris;           // number of triangles
        public int num_glcmds;         // number of opengl commands
        public int num_frames;         // total number of frames
 
        public int ofs_skins;          // offset to skin names (64 bytes each)
        public int ofs_st;             // offset to s-t texture coordinates
        public int ofs_tris;           // offset to triangles
        public int ofs_frames;         // offset to frame data
        public int ofs_glcmds;         // offset to opengl commands
        public int ofs_end;            // offset to end of file
    }
 
    struct md2_vertex
    {
        unsafe public fixed byte coords[3]; //x,y,z compressed coordinates (see frame)
        public byte light; //index to vertice's lighting normal
    }
 
    struct md2_triangle
    {     
	    unsafe public fixed short index_xyz[3];    // indexes to triangle's vertices     	
	    unsafe public fixed short index_st[3];     // indexes to vertice's texture coorinates
    }
 
    struct md2_frame
    {
        unsafe public fixed float scale[3]; //scale values for x,y,z (multiply)
        unsafe public fixed float translate[3]; //translate values for x,y,z (add)
        unsafe public fixed byte name[16]; //frame name;
    }
 
    struct md2_compressed_texture
    {
        public short s; //texture x coordinate (compressed, should be divided by skin width)
        public short t; //texture y coordinate (compressed, should be divided by skin height)
    }
 
    struct md2_glcommand
    {
        public float s;
        public float t;
        public int index;
    }

You can put these structures into a separate file in the same namespace as your program/engine.

Now for reading I used FileStream and BinaryReader like you can see below:

FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read); //instantiate the stream object
    BinaryReader br = new BinaryReader(fs); //instantiate the reader object 

What I do is read the data binary from the file and convert the block to the respective structure:

MD2_HEADER:

md2_header header; //MD2 header
 
    byte[] buff = br.ReadBytes(Marshal.SizeOf(typeof(md2_header)));//read the header as binary data
 
    GCHandle handle = GCHandle.Alloc(buff, GCHandleType.Pinned); //prevent the GC from affecting the buffer
    header = (md2_header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(md2_header)); //get the header from the buffer
    handle.Free(); //let the GC do his thing again 

Ok, now we have the header so we know where to find all the actual model data blocks:

MD2_TRIANGLES

md2_triangle trt; //MD2 triangle
 
    br.BaseStream.Seek(header.ofs_tris, SeekOrigin.Begin); //seek to the triangles offset in the MD2 file
 
    for (int i = 0; i <= header.num_tris - 1; i++)
    {
         buff = br.ReadBytes(Marshal.SizeOf(typeof(md2_triangle)));//read a triangle
 
         handle = GCHandle.Alloc(buff, GCHandleType.Pinned); //block GC
         trt = (md2_triangle)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(md2_triangle)); //get struct from binary
         handle.Free(); //free GC
 
         //add your new triangle to the triangle pool in your mesh object or whatever structure you want to use
    }

MD2_FRAMES

md2_frame ffrm; //MD2 frame
 
    br.BaseStream.Seek(header.ofs_frames, SeekOrigin.Begin); //go to first frame in the MD2 file
 
    for (int j = 0; j < header.num_frames-1; j++) //read the frames
    {
         buff = br.ReadBytes(Marshal.SizeOf(typeof(md2_frame)));
 
         handle = GCHandle.Alloc(buff, GCHandleType.Pinned);
         ffrm = (md2_frame)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(md2_frame));
         handle.Free();
 
         //at this point you have the name of the frame so you can create your frame object in the frame pool or whatever :)
 
         //now to read all the vertexes in this frame
         md2_vertex tvrt;
         float tx, ty, tz;
 
         for (int i = 0; i <= header.num_xyz - 1; i++) //for each frame read all the vertexes
         {
             buff = br.ReadBytes(Marshal.SizeOf(typeof(md2_vertex)));
 
             handle = GCHandle.Alloc(buff, GCHandleType.Pinned);
             tvrt = (md2_vertex)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(md2_vertex));
             handle.Free();
 
             tx = tvrt.coords[0] * ffrm.scale[0] + ffrm.translate[0];
             ty = tvrt.coords[1] * ffrm.scale[1] + ffrm.translate[1];
             tz = tvrt.coords[2] * ffrm.scale[2] + ffrm.translate[2];
 
             //add the vertex to the frame object...
          }
    }

MD2_TEXTURE_COORDINATES

md2_compressed_texture ctex; //MD2 compressed texture coordinates
 
    br.BaseStream.Seek(header.ofs_st, SeekOrigin.Begin); //go to start of texture coordinates
 
    for (int i = 0; i < header.num_st; i++)
    {
         buff = br.ReadBytes(Marshal.SizeOf(typeof(md2_compressed_texture)));
 
         handle = GCHandle.Alloc(buff, GCHandleType.Pinned);
         ctex = (md2_compressed_texture)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(md2_compressed_texture));
         handle.Free();
 
         //add the new texture to your textures pool, dont forget to decompress the texture coordinates (dividing them by skin width and height)
    }

Now you have just enough to start rendering of your model. What I did was to code a render_frame function that based on the supplied frame number would draw each triangle using the appropriate frame vertexes.

You might notice that I skipped the reading of normals and glcommands. They are not really necessary as you can calculate your own normals(like I did, because I load also other file formats that dont contain the precalculated normals) and glcommands are used to render the frame using triangle strips and fans (this will achieve better frame reates by lowering the ammount of data sent to opengl to render), instead you can use your own method to strippify your mesh.

DOWNLOAD

Files:Scrn.jpg

This is my MD2 viewer, the code is a mess and the methods I used for the data structures and rendering might not be the brightest but I achieved my goal to learn OpenGL in C# adn it will give you an idea how I've complicated my life with OOP and OpenGL. For any comments, suggestions or "your code sucks" messages please do not hesitate to write me an email to balex2ro@yahoo.com.

source code

Thanks to all the members here writting about the MD2 format, and thanks to the people who wrote the csgl library.

STUFF TO READ

MD2 file format on gpwiki

Cheers