MD2.H&&MD2.CPP
///////////////////////////////////////MD2.H////////////////////////////////////////////////// #ifndef _MD2_H #define _MD2_H // These are the needed defines for the max values when loading .MD2 files #define MD2_MAX_TRIANGLES 4096 #define MD2_MAX_VERTICES 2048 #define MD2_MAX_TEXCOORDS 2048 #define MD2_MAX_FRAMES 512 #define MD2_MAX_SKINS 32 #define MD2_MAX_FRAMESIZE (MD2_MAX_VERTICES * 4 + 128) // This holds the header information that is read in at the beginning of the file struct tMd2Header { int magic; // This is used to identify the file int version; // The version number of the file (Must be 8) int skinWidth; // The skin width in pixels int skinHeight; // The skin height in pixels int frameSize; // The size in bytes the frames are int numSkins; // The number of skins associated with the model int numVertices; // The number of vertices (constant for each frame) int numTexCoords; // The number of texture coordinates int numTriangles; // The number of faces (polygons) int numGlCommands; // The number of gl commands int numFrames; // The number of animation frames int offsetSkins; // The offset in the file for the skin data int offsetTexCoords; // The offset in the file for the texture data int offsetTriangles; // The offset in the file for the face data int offsetFrames; // The offset in the file for the frames data int offsetGlCommands; // The offset in the file for the gl commands data int offsetEnd; // The end of the file offset }; //用来存储当前帧所读进来的顶点 struct tMd2AliasTriangle { byte vertex[3]; byte lightNormalIndex; }; //这个存储帧的法线和顶点 struct tMd2Triangle { float vertex[3]; float normal[3]; }; // 这个存储索引到顶点和纹理坐标数组 struct tMd2Face { short vertexIndices[3]; short textureIndices[3]; }; // 此存储UV坐标 struct tMd2TexCoord { short u, v; }; // This stores the animation scale, translation and name information for a frame, plus verts struct tMd2AliasFrame { float scale[3]; float translate[3]; char name[16]; tMd2AliasTriangle aliasVertices[1]; }; // This stores the frames vertices after they have been transformed struct tMd2Frame { char strName[16]; tMd2Triangle *pVertices; }; // This stores a skin name typedef char tMd2Skin[64]; // This class handles all of the loading code class CLoadMD2 { public: CLoadMD2(); // This inits the data members // This is the function that you call to load the MD2 bool ImportMD2(t3DModel *pModel, char *strFileName, char *strTexture); private: // This reads in the data from the MD2 file and stores it in the member variables void ReadMD2Data(); // This converts the member variables to our pModel structure void ConvertDataStructures(t3DModel *pModel); // This computes the vertex normals for the object (used for lighting) void ComputeNormals(t3DModel *pModel); // This frees memory and closes the file void CleanUp(); // The file pointer FILE *m_FilePointer; // Member variables tMd2Header m_Header; // The header data tMd2Skin *m_pSkins; // The skin data tMd2TexCoord *m_pTexCoords; // The texture coordinates tMd2Face *m_pTriangles; // Face index information tMd2Frame *m_pFrames; // The frames of animation (vertices) }; #endif ///////////////////////////////////////////////////////////////////////////////// // // * QUICK NOTES * // // This file holds all of the structure and class definitions needed to load // a MD2 Quake2 file. // // // Ben Humphrey (DigiBen) // Game Programmer // DigiBen@GameTutorials.com // Co-Web Host of www.GameTutorials.com // // The Quake2 .Md2 file format is owned by ID Software. This tutorial is being used // as a teaching tool to help understand model loading and animation. This should // not be sold or used under any way for commercial use with out written conset // from ID Software. // // Quake and Quake2 are trademarks of id Software. // All trademarks used are properties of their respective owners. // // ///////////////////////////////////////MD2.CPP////////////////////////////////////////////////// //***********************************************************************// // // // - "Talk to me like I'm a 3 year old!" Programming Lessons - // // // // $Author: DigiBen digiben@gametutorials.com // // // // $Program: MD2 Loader // // // // $Description: Demonstrates how to load a Quake2 MD2 Model // // // // $Date: 2/6/02 // // // //***********************************************************************// #include "main.h" #include "Md2.h" ///////////////////////////////////////////////////////////////////////// // // This file holds the code to load the Quake2 models from a .Md2 format. // The .Md2 file is usually stored in a .zip file (don't let the extension // fool you, just rename it to .zip), depending on where you get the models // from. The CLoadMD2 class handles the loading, but we draw the model // externally on our own in main.cpp. I created a converter function // to convert to our already used model and object structures. This way // eventually we can create a model library that can load any type of // model that we support, as well as use inheritance to create a new class // for each file format for the small things that each model format needs differently. // Like the other loading tutorials, we calculate our own vertex normals. // The .Md2 format is REALLY simple to load. That is why I chose it. The // next tutorial will show how to load and animate Md2 files. Next, we // will move from key frame animation to skeletal animation with the Quake3 // .Md3 files. This is also a wonderfuly easy format to load and use. // // ///////////////////////////////// CLOAD MD2 \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ///// ///// This constructor initializes the md2 structures ///// ///////////////////////////////// CLOAD MD2 \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* CLoadMD2::CLoadMD2() { // Here we initialize our structures to 0 memset(&m_Header, 0, sizeof(tMd2Header)); // Set the pointers to null m_pSkins=NULL; m_pTexCoords=NULL; m_pTriangles=NULL; m_pFrames=NULL; } ///////////////////////////////// IMPORT MD2 \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ///// ///// This is called by the client to open the .Md2 file, read it, then clean up ///// ///////////////////////////////// IMPORT MD2 \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* bool CLoadMD2::ImportMD2(t3DModel *pModel, char *strFileName, char *strTexture) { char strMessage[255] = {0}; // Open the MD2 file in binary m_FilePointer = fopen(strFileName, "rb"); // Make sure we have a valid file pointer (we found the file) if(!m_FilePointer) { // Display an error message and don't load anything if no file was found sprintf(strMessage, "Unable to find the file: %s!", strFileName); MessageBox(NULL, strMessage, "Error", MB_OK); return false; } // Just like most file formats, there is a header that needs to be read // from the .Md2 format. If you look at the tMd2Header structure you will // find all the data that will be read in. It's nice to know up front about // the data that we will be reading. This makes it easy to just to large // binary reads using fread, instead of counting and reading chunks. // Read the header data and store it in our m_Header member variable fread(&m_Header, 1, sizeof(tMd2Header), m_FilePointer); // For some reason, .Md2 files MUST have a version of 8. I am not sure why, // but if it doesn't there is something wrong and the header was read in // incorrectly, or perhaps the file format is bad. if(m_Header.version != 8) { // Display an error message for bad file format, then stop loading sprintf(strMessage, "Invalid file format (Version not 8): %s!", strFileName); MessageBox(NULL, strMessage, "Error", MB_OK); return false; } // Now that we made sure the header had correct data, we want to read in the // rest of the data. Once the data is read in, we need to convert it to our structures. // Since we are only reading in the first frame of animation, there will only // be ONE object in our t3DObject structure, held within our pModel variable. ReadMD2Data(); // Here we pass in our model structure to it can store the read Quake data // in our own model and object structure data ConvertDataStructures(pModel); // After we have read the whole MD2 file, we want to calculate our own vertex normals. ComputeNormals(pModel); // If there is a valid texture name passed in, we want to set the texture data if(strTexture) { // Create a local material info structure tMaterialInfo texture; // Copy the name of the file into our texture file name variable strcpy(texture.strFile, strTexture); // Since there is only one texture for a .Md2 file, the ID is always 0 texture.texureId = 0; // The tile or scale for the UV's is 1 to 1 (but Quake saves off a 0-256 ratio) texture.uTile = texture.uTile = 1; // We only have 1 material for a model pModel->numOfMaterials = 1; // Add the local material info to our model's material list pModel->pMaterials.push_back(texture); } // Clean up after everything CleanUp(); // Return a success return true; } ///////////////////////////////// READ MD2 DATA \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ///// ///// This function reads in all of the model's data, except the animation frames ///// ///////////////////////////////// READ MD2 DATA \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* void CLoadMD2::ReadMD2Data() { // Create a larger buffer for the frames of animation (not fully used yet) unsigned char buffer[MD2_MAX_FRAMESIZE]; int j = 0; // Here we allocate all of our memory from the header's information m_pSkins = new tMd2Skin [m_Header.numSkins]; m_pTexCoords = new tMd2TexCoord [m_Header.numTexCoords]; m_pTriangles = new tMd2Face [m_Header.numTriangles]; m_pFrames = new tMd2Frame [m_Header.numFrames]; // Next, we start reading in the data by seeking to our skin names offset fseek(m_FilePointer, m_Header.offsetSkins, SEEK_SET); // Depending on the skin count, we read in each skin for this model fread(m_pSkins, sizeof(tMd2Skin), m_Header.numSkins, m_FilePointer); // Move the file pointer to the position in the file for texture coordinates fseek(m_FilePointer, m_Header.offsetTexCoords, SEEK_SET); // Read in all the texture coordinates in one fell swoop fread(m_pTexCoords, sizeof(tMd2TexCoord), m_Header.numTexCoords, m_FilePointer); // Move the file pointer to the triangles/face data offset fseek(m_FilePointer, m_Header.offsetTriangles, SEEK_SET); // Read in the face data for each triangle (vertex and texCoord indices) fread(m_pTriangles, sizeof(tMd2Face), m_Header.numTriangles, m_FilePointer); // Move the file pointer to the vertices (frames) fseek(m_FilePointer, m_Header.offsetFrames, SEEK_SET); // Assign our alias frame to our buffer memory tMd2AliasFrame *pFrame = (tMd2AliasFrame *) buffer; // Allocate the memory for the first frame of animation's vertices m_pFrames[0].pVertices = new tMd2Triangle [m_Header.numVertices]; // Read in the first frame of animation fread(pFrame, 1, m_Header.frameSize, m_FilePointer); // Copy the name of the animation to our frames array strcpy(m_pFrames[0].strName, pFrame->name); // After we have read in the data for the model, since there is animation, // This means that there are scale and translation values to be dealt with. // To apply the scale and translation values, we simply multiply the scale (x, y, z) // by the current vertex (x, y, z). Also notice that we switch the Y and Z values // so that Y is faces up, NOT Z. // Store off a vertex array pointer to cut down large lines of code tMd2Triangle *pVertices = m_pFrames[0].pVertices; // Go through all of the number of vertices and assign the scale and translations. // Store the vertices in our current frame's vertex list array, while swapping Y and Z. // Notice we also negate the Z axis as well to make the swap correctly. for (j=0; j < m_Header.numVertices; j++) { pVertices[j].vertex[0] = pFrame->aliasVertices[j].vertex[0] * pFrame->scale[0] + pFrame->translate[0]; pVertices[j].vertex[2] = -1 * (pFrame->aliasVertices[j].vertex[1] * pFrame->scale[1] + pFrame->translate[1]); pVertices[j].vertex[1] = pFrame->aliasVertices[j].vertex[2] * pFrame->scale[2] + pFrame->translate[2]; } } ///////////////////////////////// CONVERT DATA STRUCTURES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ///// ///// This function converts the .md2 structures to our own model and object structures ///// ///////////////////////////////// CONVERT DATA STRUCTURES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* void CLoadMD2::ConvertDataStructures(t3DModel *pModel) { int j = 0, i = 0; // Assign the number of objects, which is 1 since we only want 1 frame // of animation. In the next tutorial each object will be a key frame // to interpolate between. pModel->numOfObjects = 1; // Create a local object to store the first frame of animation's data t3DObject currentFrame = {0}; // Assign the vertex, texture coord and face count to our new structure currentFrame.numOfVerts = m_Header.numVertices; currentFrame.numTexVertex = m_Header.numTexCoords; currentFrame.numOfFaces = m_Header.numTriangles; // Allocate memory for the vertices, texture coordinates and face data. currentFrame.pVerts = new CVector3 [currentFrame.numOfVerts]; currentFrame.pTexVerts = new CVector2 [currentFrame.numTexVertex]; currentFrame.pFaces = new tFace [currentFrame.numOfFaces]; // Go through all of the vertices and assign them over to our structure for (j=0; j < currentFrame.numOfVerts; j++) { currentFrame.pVerts[j].x = m_pFrames[0].pVertices[j].vertex[0]; currentFrame.pVerts[j].y = m_pFrames[0].pVertices[j].vertex[1]; currentFrame.pVerts[j].z = m_pFrames[0].pVertices[j].vertex[2]; } // We can now free the old vertices stored in this frame of animation delete m_pFrames[0].pVertices; // Go through all of the uv coordinates and assign them over to our structure. // The UV coordinates are not normal uv coordinates, they have a pixel ratio of // 0 to 256. We want it to be a 0 to 1 ratio, so we divide the u value by the // skin width and the v value by the skin height. This gives us our 0 to 1 ratio. // For some reason also, the v coodinate is flipped upside down. We just subtract // the v coordinate from 1 to remedy this problem. for (j=0; j < currentFrame.numTexVertex; j++) { currentFrame.pTexVerts[j].x = m_pTexCoords[j].u / float(m_Header.skinWidth); currentFrame.pTexVerts[j].y = 1 - m_pTexCoords[j].v / float(m_Header.skinHeight); } // Go through all of the face data and assign it over to OUR structure for(j=0; j < currentFrame.numOfFaces; j++) { // Assign the vertex indices to our face data currentFrame.pFaces[j].vertIndex[0] = m_pTriangles[j].vertexIndices[0]; currentFrame.pFaces[j].vertIndex[1] = m_pTriangles[j].vertexIndices[1]; currentFrame.pFaces[j].vertIndex[2] = m_pTriangles[j].vertexIndices[2]; // Assign the texture coord indices to our face data currentFrame.pFaces[j].coordIndex[0] = m_pTriangles[j].textureIndices[0]; currentFrame.pFaces[j].coordIndex[1] = m_pTriangles[j].textureIndices[1]; currentFrame.pFaces[j].coordIndex[2] = m_pTriangles[j].textureIndices[2]; } // Here we add the current object (or frame) to our list object list pModel->pObject.push_back(currentFrame); } ///////////////////////////////// CLEAN UP \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ///// ///// This function cleans up our allocated memory and closes the file ///// ///////////////////////////////// CLEAN UP \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* void CLoadMD2::CleanUp() { // This just just the regular cleanup or our md2 model class. We can free // all of this data because we already have it stored in our own structures. fclose(m_FilePointer); // Close the current file pointer if(m_pSkins) delete [] m_pSkins; // Free the skins data if(m_pTexCoords) delete m_pTexCoords; // Free the texture coord data if(m_pTriangles) delete m_pTriangles; // Free the triangle face data if(m_pFrames) delete m_pFrames; // Free the frames of animation } // *Note* // // Below are some math functions for calculating vertex normals. We want vertex normals // because it makes the lighting look really smooth and life like. You probably already // have these functions in the rest of your engine, so you can delete these and call // your own. I wanted to add them so I could show how to calculate vertex normals. ////////////////////////////// Math Functions ////////////////////////////////* // This computes the magnitude of a normal. (magnitude = sqrt(x^2 + y^2 + z^2) #define Mag(Normal) (sqrt(Normal.x*Normal.x + Normal.y*Normal.y + Normal.z*Normal.z)) // This calculates a vector between 2 points and returns the result CVector3 Vector(CVector3 vPoint1, CVector3 vPoint2) { CVector3 vVector; // The variable to hold the resultant vector vVector.x = vPoint1.x - vPoint2.x; // Subtract point1 and point2 x's vVector.y = vPoint1.y - vPoint2.y; // Subtract point1 and point2 y's vVector.z = vPoint1.z - vPoint2.z; // Subtract point1 and point2 z's return vVector; // Return the resultant vector } // This adds 2 vectors together and returns the result CVector3 AddVector(CVector3 vVector1, CVector3 vVector2) { CVector3 vResult; // The variable to hold the resultant vector vResult.x = vVector2.x + vVector1.x; // Add Vector1 and Vector2 x's vResult.y = vVector2.y + vVector1.y; // Add Vector1 and Vector2 y's vResult.z = vVector2.z + vVector1.z; // Add Vector1 and Vector2 z's return vResult; // Return the resultant vector } // This divides a vector by a single number (scalar) and returns the result CVector3 DivideVectorByScaler(CVector3 vVector1, float Scaler) { CVector3 vResult; // The variable to hold the resultant vector vResult.x = vVector1.x / Scaler; // Divide Vector1's x value by the scaler vResult.y = vVector1.y / Scaler; // Divide Vector1's y value by the scaler vResult.z = vVector1.z / Scaler; // Divide Vector1's z value by the scaler return vResult; // Return the resultant vector } // This returns the cross product between 2 vectors CVector3 Cross(CVector3 vVector1, CVector3 vVector2) { CVector3 vCross; // The vector to hold the cross product // Get the X value vCross.x = ((vVector1.y * vVector2.z) - (vVector1.z * vVector2.y)); // Get the Y value vCross.y = ((vVector1.z * vVector2.x) - (vVector1.x * vVector2.z)); // Get the Z value vCross.z = ((vVector1.x * vVector2.y) - (vVector1.y * vVector2.x)); return vCross; // Return the cross product } // This returns the normal of a vector CVector3 Normalize(CVector3 vNormal) { double Magnitude; // This holds the magitude Magnitude = Mag(vNormal); // Get the magnitude vNormal.x /= (float)Magnitude; // Divide the vector's X by the magnitude vNormal.y /= (float)Magnitude; // Divide the vector's Y by the magnitude vNormal.z /= (float)Magnitude; // Divide the vector's Z by the magnitude return vNormal; // Return the normal } ///////////////////////////////// COMPUTER NORMALS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* ///// ///// This function computes the normals and vertex normals of the objects ///// ///////////////////////////////// COMPUTER NORMALS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\* void CLoadMD2::ComputeNormals(t3DModel *pModel) { CVector3 vVector1, vVector2, vNormal, vPoly[3]; // If there are no objects, we can skip this part if(pModel->numOfObjects <= 0) return; // What are vertex normals? And how are they different from other normals? // Well, if you find the normal to a triangle, you are finding a "Face Normal". // If you give OpenGL a face normal for lighting, it will make your object look // really flat and not very round. If we find the normal for each vertex, it makes // the smooth lighting look. This also covers up blocky looking objects and they appear // to have more polygons than they do. Basically, what you do is first // calculate the face normals, then you take the average of all the normals around each // vertex. It's just averaging. That way you get a better approximation for that vertex. // Go through each of the objects to calculate their normals for(int index = 0; index < pModel->numOfObjects; index++) { // Get the current object t3DObject *pObject = &(pModel->pObject[index]); // Here we allocate all the memory we need to calculate the normals CVector3 *pNormals = new CVector3 [pObject->numOfFaces]; CVector3 *pTempNormals = new CVector3 [pObject->numOfFaces]; pObject->pNormals = new CVector3 [pObject->numOfVerts]; // Go though all of the faces of this object for(int i=0; i < pObject->numOfFaces; i++) { // To cut down LARGE code, we extract the 3 points of this face vPoly[0] = pObject->pVerts[pObject->pFaces[i].vertIndex[0]]; vPoly[1] = pObject->pVerts[pObject->pFaces[i].vertIndex[1]]; vPoly[2] = pObject->pVerts[pObject->pFaces[i].vertIndex[2]]; // Now let's calculate the face normals (Get 2 vectors and find the cross product of those 2) vVector1 = Vector(vPoly[0], vPoly[2]); // Get the vector of the polygon (we just need 2 sides for the normal) vVector2 = Vector(vPoly[2], vPoly[1]); // Get a second vector of the polygon vNormal = Cross(vVector1, vVector2); // Return the cross product of the 2 vectors (normalize vector, but not a unit vector) pTempNormals[i] = vNormal; // Save the un-normalized normal for the vertex normals vNormal = Normalize(vNormal); // Normalize the cross product to give us the polygons normal pNormals[i] = vNormal; // Assign the normal to the list of normals } //////////////// Now Get The Vertex Normals ///////////////// CVector3 vSum = {0.0, 0.0, 0.0}; CVector3 vZero = vSum; int shared=0; for (i = 0; i < pObject->numOfVerts; i++) // Go through all of the vertices { for (int j = 0; j < pObject->numOfFaces; j++) // Go through all of the triangles { // Check if the vertex is shared by another face if (pObject->pFaces[j].vertIndex[0] == i || pObject->pFaces[j].vertIndex[1] == i || pObject->pFaces[j].vertIndex[2] == i) { vSum = AddVector(vSum, pTempNormals[j]);// Add the un-normalized normal of the shared face shared++; // Increase the number of shared triangles } } // Get the normal by dividing the sum by the shared. We negate the shared so it has the normals pointing out. pObject->pNormals[i] = DivideVectorByScaler(vSum, float(-shared)); // Normalize the normal for the final vertex normal pObject->pNormals[i] = Normalize(pObject->pNormals[i]); vSum = vZero; // Reset the sum shared = 0; // Reset the shared } // Free our memory and start over on the next object delete [] pTempNormals; delete [] pNormals; } } ///////////////////////////////////////////////////////////////////////////////// // // * QUICK NOTES * // // Pretty simple huh? This is probably the easiest 3D file format I have ever // worked with, so good job Carmack! Once again, the next Md2 tutorial will cover // the key frame animation that is associated with these models. Then you can // actually say you have worked with real quake characters and know how they did // their animation. Let's go over a brief explanation of this loader: // // The structures MUST be the same size and data types in order to load the // Quake2 data. First we load the Header information. This tells us everything // about the file and it's contents. // // After the header is loaded, we need to check if the ID is 8. This is a must. // Don't ask me why it's 8, ask John Carmack! If the version ID checks out, then // we can start loading the data. // // For each set of data you want to load is, we use an fseek() to move the file // pointer to that location in the file that is given in the header. // // After you load the data, you can then convert the data structures to your own // format, that way you don't have ot be stuck with theirs. I decided to make it // like the other loaders for future purposes. We also compute our own normals. // // There is one thing I didn't mention that was NOT loaded in. There is an array // of OpenGL commands that allow you to render the vertices in triangle strips and // a triangle fan. This is the ugliest code I have ever seen to implement it, so // I left it out :) // // I would like to thank Daniel E. Schoenblum <dansch@hops.cs.jhu.edu> for help // with explaining the file format. // // Let me know if this helps you out! // // // Ben Humphrey (DigiBen) // Game Programmer // DigiBen@GameTutorials.com // Co-Web Host of www.GameTutorials.com // // The Quake2 .Md2 file format is owned by ID Software. This tutorial is being used // as a teaching tool to help understand model loading and animation. This should // not be sold or used under any way for commercial use with out written conset // from ID Software. // // Quake and Quake2 are trademarks of id Software. // All trademarks used are properties of their respective owners. // //