#include "MD2Loader.h" // File reading #include #include using namespace std; // BYTE added in case Windows.h is not included. typedef unsigned char BYTE; // Magic number for MD2 files "IDP2" or 844121161 const int MD2_IDENT = (('2'<<24) + ('P'<<16) + ('D'<<8) + 'I'); // MS2 version const int MD2_VERSION = 8; struct Md2Header { int indent; // The magic number used to identify the file. int version; // The file version number (must be 8). int skinWidth; // The width in pixels of our image. int skinHeight; // The height in pixels of our image. 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. 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 animated 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 Md2Triangle { short vertexIndex[3]; // Vertex indices of the triangle short uvIndex[3]; // Texture coordinate indices }; struct Md2Vertex { BYTE v[3]; // Compressed vertex (x, y, z) coordinates BYTE lightNormalIndex; // Index to a normal vector for the lighting }; // Texture co-ordinates struct Md2TextureCoord { short textureCoord[2]; }; struct Md2Frame { float scale[3]; // Scale values float translate[3]; // Translation vector char name[16]; // Frame name Md2Vertex verts[1]; // First vertex of this frame }; struct PcxHeader { BYTE ID; BYTE Version; BYTE Encoding; BYTE BitsPerPixel; short XMin; short YMin; short XMax; short YMax; short HRes; short VRes; BYTE ClrMap[16 * 3]; BYTE Reserved; BYTE NumPlanes; short BytesPerLine; short Pal; BYTE Filler[58]; }; MD2Loader::MD2Loader() { } MD2Loader::~MD2Loader() { } bool LoadPCX(const char* textureFilename, Texture& texture, const Md2Header* md2Header) { std::ifstream file; BYTE * paletteIndices = texture.GetPaletteIndices(); COLORREF * palette = texture.GetPalette(); // Try to open file file.open(textureFilename, std::ios::in | std::ios::binary); if (file.fail()) { return false; } // Read PCX header PcxHeader header; file.read(reinterpret_cast(&header), sizeof(PcxHeader)); // Verify that this is a valid PCX file // We only handle those with 256 colour palette if ((header.Version != 5) || (header.BitsPerPixel != 8) || (header.Encoding != 1) || (header.NumPlanes != 1) || (md2Header && (header.BytesPerLine != md2Header->skinWidth))) { // This is not valid supported PCX file.close(); return false; } // Check dimensions int xSize = header.XMax - header.XMin + 1; int ySize = header.YMax - header.YMin + 1; int size = xSize * ySize; // Check that this matches our MD2 expected texture // Note. valid size is <= because uses RLE (so potentially smaller) if (md2Header && (size > (md2Header->skinHeight * md2Header->skinWidth))) { // Doesn't match expected MD2 skin size file.close(); return false; } // Reading file data BYTE processByte, colourByte; int count = 0; while (count < size) { file.read(reinterpret_cast(&processByte), 1); // Run length encoding - test if byte is an RLE byte if ((processByte & 192) == 192) { // Extract number of times repeated byte processByte &= 63; file.read(reinterpret_cast(&colourByte), 1); for (int index = 0; index < processByte; ++index) { // repeatedly write colour paletteIndices[count] = colourByte; ++count; } } else { // Byte is the colour paletteIndices[count] = processByte; ++count; } } bool returnValue = false; // read palette data... file.seekg(-769, std::ios::end); // This offset from end of file file.read(reinterpret_cast(&processByte), 1); if (processByte == 12) { BYTE rawPalette[768]; file.read(reinterpret_cast(&rawPalette), 768); // Build palette for (int palIndex = 0; palIndex < 256; ++palIndex) { palette[palIndex] = RGB(rawPalette[palIndex * 3], rawPalette[(palIndex * 3) + 1], rawPalette[(palIndex * 3) + 2]); } returnValue = true; } file.close(); return returnValue; } // Load model from file. bool MD2Loader::LoadModel(const char* md2Filename, const char * textureFilename, Model& model, AddPolygon addPolygon, AddVertex addVertex, AddTextureUV addTextureUV) { ifstream file; Md2Header header; bool bHasTexture = false; // Try to open MD2 file file.open(md2Filename, ios::in | ios::binary); if (file.fail()) { return false; } // Read file header file.read(reinterpret_cast(&header), sizeof(Md2Header)); // Verify that this is a MD2 file (check for the magic number and version number) if ((header.indent != MD2_IDENT) && (header.version != MD2_VERSION)) { // This is not a MD2 model file.close(); return false; } // Allocate the memory we need Md2Triangle* triangles = new Md2Triangle[header.numTriangles]; // We are only interested in the first frame BYTE* frameBuffer = new BYTE[header.frameSize]; Md2Frame* frame = reinterpret_cast(frameBuffer); Md2TextureCoord * textureCoords = new Md2TextureCoord[header.numTexCoords]; // Read polygon data... file.seekg(header.offsetTriangles, ios::beg); file.read(reinterpret_cast(triangles), sizeof(Md2Triangle) * header.numTriangles); // Read frame data... file.seekg(header.offsetFrames, ios::beg); file.read(reinterpret_cast(frame), header.frameSize); // Read texture coordinate data file.seekg(header.offsetTexCoords, std::ios::beg); file.read(reinterpret_cast(textureCoords), sizeof(Md2TextureCoord) * header.numTexCoords); // Close the file file.close(); // Attempt to load any texture if (textureFilename) { model.GetTexture().SetTextureSize(header.skinWidth, header.skinHeight); bHasTexture = LoadPCX(textureFilename, model.GetTexture(), &header); } // Polygon array initialization for ( int i = 0; i < header.numTriangles; ++i ) { // Call supplied member function to add a new polygon to the list std::invoke(addPolygon, model, triangles[i].vertexIndex[0], triangles[i].vertexIndex[1], triangles[i].vertexIndex[2], triangles[i].uvIndex[0], triangles[i].uvIndex[1], triangles[i].uvIndex[2]); } // Vertex array initialization for( int i = 0; i < header.numVertices; ++i ) { // The following are the expressions needed to access each of the co-ordinates. // // X co-ordinate: frame->verts[i].v[0] * frame->scale[0] + frame->translate[0] // Y co-ordinate: frame->verts[i].v[2] * frame->scale[2] + frame->translate[2] // Z co-ordinate: frame->verts[i].v[1] * frame->scale[1] + frame->translate[1] // // NOTE: We have to swap Y and Z over because Z is up in MD2 and we have Y as up-axis std::invoke(addVertex, model, static_cast((frame->verts[i].v[0] * frame->scale[0]) + frame->translate[0]), static_cast((frame->verts[i].v[2] * frame->scale[2]) + frame->translate[2]), static_cast((frame->verts[i].v[1] * frame->scale[1]) + frame->translate[1])); } // Texture coordinates initialisation if (bHasTexture) { for (int i = 0; i < header.numTexCoords; i++) { std::invoke(addTextureUV, model, textureCoords[i].textureCoord[0], textureCoords[i].textureCoord[1]); } } // Free dynamically allocated memory delete [] triangles; // NOTE: this is 'array' delete. Must be sure to use this triangles = 0; delete [] frameBuffer; frameBuffer = 0; frame = 0; delete[] textureCoords; textureCoords = 0; return true; }