Thread: problems with MD2 file format

  1. #1
    Registered User
    Join Date
    Mar 2007
    Posts
    416

    problems with MD2 file format

    I'm trying to make an importer for MD2 models. I can load the header correctly, and get to the correct offset to load what I want. What I'm having trouble with is getting the values from those offsets. I've downloaded and looked at a ton of tutorials on loading MD2 models but the problem with a lot of them is they are specific to the person who made them. So I'm getting confused on how do I correctly load the information from the offsets. I found file format specs that tell me the size of everything in the header, which is useful, but then how do I know what to use/read for say, the faces, or frames. I found the faces to be 12 bytes for each face (the frame size is part of the header so thats ok), but how do I know I should have X amount of elements in it. Like say short Vert[3] and short UV[3] vs something like int Vert[3] (which is 12 bytes by itself). Same goes for frames. I wish there was a detailed format spec someone could link me to that tells everything about each part that needs to be loaded. Below is what I have, and also a copied output of the header file information so I know its being read correctly.

    One last question, the nTextures variable in the header is always zero on the models I've made, and downloaded even though they load textures when the example program is run, what gives?

    Code:
    struct MD2FILEHEADER
    {
        int ID;             //always set to IDP2 (0x32504449)
        int Version;        //always set to 8 (maybe check this?)
        int TexWidth;       //texture width in pixels
        int TexHeight;      //texture height in pixels
        int FrameSize;      //size of each frame in bytes
        int nTextures;      //number of textures
        int nVertices;      //number of vertices from all frames
        int nTexCoords;     //number of texture coordinates
        int nFaces;         //number of faces in the model
        int nGLCommands;    //number of GL commands
        int nFrames;        //number of frames
    
        int TexOffset;      //An offset to the texture name(s) 
        int UVOffset;       //An offset to the UV coordinates
        int FaceOffset;     //An offset to the face data
        int FrameOffset;    //An offset to the first animation frame
        int GLCmdOffset;    //An offset to the GLCmd list
        int EOFOffset;      //An offset to the end of the file
    }; //end MD2FILEHEADER struct
    
    
    struct MD2UVCOORD
    {
        short U; //Divide by TexWidth
        short V; //Divide by TexHeight
    };
    
    struct MD2FACE
    {
        short Vertex[3];
        short UV[3];
    };
    
    struct MD2VERTEX
    {
        byte Vtx[3];
        byte Normal;
    };
    
    struct MD2FRAME
    {
        float Scale[3];
        float Translate[3];
        char Name[16];
    };
    
    
    //the loading function that is part of a class (only function right now)
    bool MD2::LoadMD2File(string path)
    {
        FILE *md2file;              //file pointer
        MD2FILEHEADER md2header;    //declare header structure    
        
        md2file = fopen(path.c_str(),"rb"); //open the file for reading
        if(!md2file) return false; //could not open file
        
        fread(&md2header,sizeof(md2header),1,md2file); //read header information
        if(md2header.ID != 844121161) return false; //file is not an md2 file
        
        //declare all the needed structures
        MD2FACE md2face [md2header.nFaces];
        MD2FRAME md2frame [md2header.nFrames];
        
        fseek(md2file,md2header.FrameOffset,SEEK_SET); //move to frame
        for(int i = 0; i < md2header.nFrames; i++){
            fread(&md2frame[i],sizeof(MD2FRAME),1,md2file);
        }
        
        fseek(md2file,md2header.FaceOffset,SEEK_SET); //move to face
        for(int i = 0; i < md2header.nFaces; i++){
            fread(&md2face[i],sizeof(MD2FACE),1,md2file);
        }
        
        return true;    
    } //end LoadMD2File();
    Code:
    //the output
    
    ID: 844121161
    Version: 8
    TexWidth: 256
    TexHeight: 256
    Frame size: 3956
    nTextures: 0
    nVertices: 979
    nTexCoords: 931
    nFaces: 1896
    nGLCommands: 18961
    nFrames: 400
    TexOffset: 68
    UVOffset: 68
    FaceOffset: 3792
    FrameOffset: 26544
    GLCmdOffset: 1608944
    EOFOffset: 1684788

  2. #2
    Frequently Quite Prolix dwks's Avatar
    Join Date
    Apr 2005
    Location
    Canada
    Posts
    8,057
    It's pretty much irrelevant, but here:
    Code:
        //declare all the needed structures
        MD2FACE md2face [md2header.nFaces];
        MD2FRAME md2frame [md2header.nFrames];
    If you're doing what I think you're doing (declaring a variable-length array), then you're using a non-standard feature of g++. (It's part of C99, but not C++ or C89.)

    And you're using the C API in C++, but hey, I probably would too, being familiar with it.

    This might be part of the answer to your question, I'm not sure.
    SKINS

    There is an array of numSkins skin names stored at offsetSkins into the file. Each skin name is a char[64]. The name is really a path to the skin, relative to the base game directory (baseq2 f or "standard" Quake2). The skin files are regular pcx files.

    Code:
    typedef struct
    {
       short s, t;
    } textureCoordinate_t;
    short s, t: These two shorts are used to map a vertex onto a skin. The horizontal axis position is given by s, and the vertical axis position is given by t. The range for s is greater than or equal to 0 and less than skinWidth< /a> (0 <= s < skinWidth). The range for t is greater than or equal to 0 and less than skinHeight (0 <= s < skinHeight). N ote that the ranges are different than in the s and t members of the glCommandVertex structure.
    dwk

    Seek and ye shall find. quaere et invenies.

    "Simplicity does not precede complexity, but follows it." -- Alan Perlis
    "Testing can only prove the presence of bugs, not their absence." -- Edsger Dijkstra
    "The only real mistake is the one from which we learn nothing." -- John Powell


    Other boards: DaniWeb, TPS
    Unofficial Wiki FAQ: cpwiki.sf.net

    My website: http://dwks.theprogrammingsite.com/
    Projects: codeform, xuni, atlantis, nort, etc.

  3. #3
    Registered User
    Join Date
    Mar 2007
    Posts
    416
    That would help, but the other problem is when I go to the skin offset the size of the skin names is 0 bytes. The header starts the skin offset at 68, and then starts the UV offset at 68 so the skin size is obviously zero (in the output header i posted). Again this happens on all my md2 files, even the ones that load pcx textures correctly.

    Basically I am trying to understand how to get the correct amount of data from a certain offset, and store them in the correct data variable. That way I don't have UV coords popping up in vertex coords or something. As an example, I thought I got the frame information correctly (which contains only the scale, translate, and name information right?) but when I output the scale information to a console it gives me values like 2.3648e+109 which in my opinion isn't right (i could be wrong though). I just can't seem to find any details on how frames, UV coords, vertices, so on, are stored at whatever offset it may be.

    EDIT: I found another program that can export md2 files and this time it exports the correct texture number, and name. Although getting the name is another story. So the texture problem is solved, but I'm still trying to figure out how to get the correct vertexes and such.
    Last edited by scwizzo; 12-27-2008 at 11:58 AM.

  4. #4
    Registered User
    Join Date
    Mar 2007
    Posts
    416
    I figured the major parts of it out. The way the vertex data gets loaded is by loading the scale/translate/frame name information, then looping through the rest of the frame size (in bytes) to get the vertex points for each frame. For future reference and anyone who might care it looks something like this...
    Code:
        fseek(md2file,md2header.FrameOffset,SEEK_SET); //move to frame offset
        for(int i = 0; i < md2header.nFrames; i++){ //loop through equal to number of frames
            fread(&frame,sizeof(frame),1,md2file); //read the scale, translate, name info
    
            for(int n = 0; n < (md2header.FrameSize - sizeof(MD2FRAME)) / 4; n++){
                fread(&verts,sizeof(MD2VERTEX),1,md2file); //read in all the vertex points for the rest of the frame
                                                           //every time it loops through it will erase the verts struct 
                                                           //(i plan on assigning verts to something else, this is why)
            }
        }
    I will edit this post with whatever other useful things I find out that I did not find anywhere else (like gpwiki, google, so on).

    EDIT: I guess I need some help on the rendering part. As you can see in the attached picture something weird happened with the rendering of my torus. Spheres, people, birds.... dwarfs... they all look weird like that too. My render code is below also. Do I need to load the GLCommands from the MD2 file, or is is ok to assume they are always triangles? I also already decompressed the vertex data with the scale and translations from the frame.

    Code:
    bool MD2::RenderMD2Objects(int frame)
    {
        MD2MODEL mdl = mdls.front();
        
        /*render all objects to the screen*/
        glPolygonMode( GL_BACK, GL_FILL );                  //draw back face
        glPolygonMode( GL_FRONT, GL_FILL );                 //draw front face
        
        glEnable(GL_NORMALIZE);
        glBegin(GL_TRIANGLES);
        
        for(int i = 0; i < mdl.nFaces; i++){
            
            //the vertexes are stored in vectors, to render each frame the vector index needs to be multiplied by the frame number
            glVertex3f( mdl.v_x[ mdl.vi_x[i]*frame ],
                        mdl.v_y[ mdl.vi_y[i]*frame ],
                        mdl.v_z[ mdl.vi_z[i]*frame ]);
        }
        glEnd();
        
        return true;
    }

    EDIT #2: Ok so I fixed it. I looped through the triangle making loops with respect to each face, but did not make the other 2 vertex points in order to make the complete face. Here is the working render code for future people. Also, just from my experience, the code out there can vary and be so specific to one person that it can be hard to try and make someone's code work for you, but never hurts to try.

    Code:
    glBegin(GL_TRIANGLES);
        
        for(int i = 0; i < mdl.nFaces; i++){
            
            glVertex3f( mdl.v_x[ mdl.vi_x[i]*frame ],
                        mdl.v_y[ mdl.vi_x[i]*frame ],
                        mdl.v_z[ mdl.vi_x[i]*frame ]);
                        
            glVertex3f( mdl.v_x[ mdl.vi_y[i]*frame ],
                        mdl.v_y[ mdl.vi_y[i]*frame ],
                        mdl.v_z[ mdl.vi_y[i]*frame ]);
            
            glVertex3f( mdl.v_x[ mdl.vi_z[i]*frame ],
                        mdl.v_y[ mdl.vi_z[i]*frame ],
                        mdl.v_z[ mdl.vi_z[i]*frame ]);
        }
        glEnd();
    Last edited by scwizzo; 12-28-2008 at 05:54 PM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. A development process
    By Noir in forum C Programming
    Replies: 37
    Last Post: 07-10-2011, 10:39 PM
  2. Inventory records
    By jsbeckton in forum C Programming
    Replies: 23
    Last Post: 06-28-2007, 04:14 AM
  3. help with text input
    By Alphawaves in forum C Programming
    Replies: 8
    Last Post: 04-08-2007, 04:54 PM
  4. System
    By drdroid in forum C++ Programming
    Replies: 3
    Last Post: 06-28-2002, 10:12 PM
  5. File I/O problems!!! Help!!!
    By Unregistered in forum C Programming
    Replies: 4
    Last Post: 05-17-2002, 08:09 PM