/* orxonox - the future of 3D-vertical-scrollers Copyright (C) 2004 orx This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. ### File Specific: main-programmer: Patrick Boenzli */ #define DEBUG_SPECIAL_MODULE DEBUG_MODULE_IMPORTER #include "md2Model.h" #include "material.h" #include #include #include using namespace std; sVec3D MD2Model2::anorms[NUM_VERTEX_NORMALS] = { #include "anorms.h" }; float MD2Model2::anormsDots[SHADEDOT_QUANT][256] = { #include "anormtab.h" }; static float *shadeDots = MD2Model2::anormsDots[0]; float md2Angle = 0.0f; sAnim MD2Model2::animationList[21] = { { 0, 39, 9 }, // STAND { 40, 45, 10 }, // RUN { 46, 53, 10 }, // ATTACK { 54, 57, 7 }, // PAIN_A { 58, 61, 7 }, // PAIN_B { 62, 65, 7 }, // PAIN_C { 66, 71, 7 }, // JUMP { 72, 83, 7 }, // FLIP { 84, 94, 7 }, // SALUTE { 95, 111, 10 }, // FALLBACK { 112, 122, 7 }, // WAVE { 123, 134, 6 }, // POINT { 135, 153, 10 }, // CROUCH_STAND { 154, 159, 7 }, // CROUCH_WALK { 160, 168, 10 }, // CROUCH_ATTACK { 196, 172, 7 }, // CROUCH_PAIN { 173, 177, 5 }, // CROUCH_DEATH { 178, 183, 7 }, // DEATH_FALLBACK { 184, 189, 7 }, // DEATH_FALLFORWARD { 190, 197, 7 }, // DEATH_FALLBACKSLOW { 198, 198, 5 }, // BOOM }; /******************************************************************************** * MD2MODEL * ********************************************************************************/ /** \brief standard constructor creates a new model */ MD2Model::MD2Model () { this->setClassName ("MD2Model"); MD2Loader* md2loader = new MD2Loader(); this->model = new t3DModel; md2loader->importMD2(this->model, "../data/models/tris.md2"); delete md2loader; } /** \brief standard deconstructor */ MD2Model::~MD2Model () { // delete what has to be deleted here } void MD2Model::animate() { if( unlikely(this->model->objectList.size() <= 0)) return; /* get current animation from the list */ tAnimationInfo *pAnim = &(this->model->animationList[this->model->currentAnim]); int nextFrame = (this->model->currentFrame + 1) % pAnim->endFrame; if( unlikely(nextFrame == 0)) nextFrame = pAnim->startFrame; t3DObject *pFrame = &this->model->objectList[this->model->currentFrame]; t3DObject *pNextFrame = &this->model->objectList[nextFrame]; /* we have stored the texture and face information only in the first frame */ t3DObject *pFirstFrame = &this->model->objectList[0]; /* get the current time as a value in the domain [0..1] :)) */ float t = this->getCurrentTime(this->model, nextFrame); glBegin(GL_TRIANGLES); for(int j = 0; j < pFrame->numOfFaces; j++) { for(int whichVertex = 0; whichVertex < 3; whichVertex++) { int vertIndex = pFirstFrame->pFaces[j].vertIndex[whichVertex]; int texIndex = pFirstFrame->pFaces[j].coordIndex[whichVertex]; if( likely(pFirstFrame->pTexVerts != NULL)) { glTexCoord2f(pFirstFrame->pTexVerts[texIndex].x, pFirstFrame->pTexVerts[texIndex].y); } /* here comes the interpolation part */ CVector3 vPoint1 = pFrame->pVerts[vertIndex]; CVector3 vPoint2 = pNextFrame->pVerts[vertIndex]; glVertex3f(vPoint1.x + t * (vPoint2.x - vPoint1.x), vPoint1.y + t * (vPoint2.y - vPoint1.y), vPoint1.z + t * (vPoint2.z - vPoint1.z)); } } glEnd(); } float MD2Model::getCurrentTime(t3DModel *pModel, int nextFrame) { /* stretch the time with animation speed (and make seconds out of it) */ //float t = this->dtS / kAnimationSpeed; if ( unlikely(this->localTime*1000.0 >= 1000.0/kAnimationSpeed) ) { pModel->currentFrame = nextFrame; this->localTime = 0.0f; } return (this->localTime / kAnimationSpeed); } void MD2Model::tick(float dtS) { this->localTime += dtS; } /** \brief draw function these function will take NO argument in the final version, just for testing */ void MD2Model::draw() { if( this->model->objectList.size() <= 0) return; t3DObject *pObject = &this->model->objectList[0]; glBegin(GL_TRIANGLES); for(int j = 0; j < pObject->numOfFaces; j++) { for(int whichVertex = 0; whichVertex < 3; whichVertex++) { int index = pObject->pFaces[j].vertIndex[whichVertex]; int index2 = pObject->pFaces[j].coordIndex[whichVertex]; /* we invert the normals since the md2 file format uses different style */ /* FIX FIX FIX: ther are actualy no reasons to compute the normals every frame: change this later*/ glNormal3f(-pObject->pNormals[index].x, -pObject->pNormals[index].y, -pObject->pNormals[index].z); if( likely(pObject->pTexVerts != NULL)) { glTexCoord2f(pObject->pTexVerts[index2].x, pObject->pTexVerts[index2].y); } glVertex3f(pObject->pVerts[index].x, pObject->pVerts[index].y, pObject->pVerts[index].z); } } glEnd(); } /******************************************************************************** * MD2LOADER * ********************************************************************************/ /** \brief standard deconstructor creates a new model loader */ MD2Loader::MD2Loader() { this->setClassName ("MD2Loader"); /* initialize all data to initial state */ memset(&this->header, 0, sizeof(tMd2Header)); this->pSkins = NULL; this->pTexCoords = NULL; this->pTriangles = NULL; this->pFrames = NULL; } /** \brief standard deconstructor */ MD2Loader::~MD2Loader() {} /** \brief this is called by the client to open the .Md2 file, read it, then clean up \param model to load in \param file name to load \param texture name to load */ bool MD2Loader::importMD2(t3DModel *pModel, char *fileName, char *textureName) { this->pFile = fopen(fileName, "rb"); if( unlikely(!pFile)) { PRINTF(1)("Couldn't open the MD2 File for loading. Exiting.\n"); return false; } fread(&this->header, 1, sizeof(tMd2Header), pFile); /* check for the header version: make sure its a md2 file :) */ if( likely(this->header.version != 8)) { PRINTF(1)("Couldn't load file %s: invalid file format: stop loading\n", fileName); return false; } this->readMD2Data(); this->convertDataStructures(pModel); this->computeNormals(pModel); if( likely((int)textureName)) { tMaterialInfo textureInfo; strcpy(textureInfo.strFile, textureName); /* since there is only one texture for a .Md2 file, the ID is always 0 */ textureInfo.texureId = 0; textureInfo.uTile = 1; /* we only have 1 material for a model */ pModel->numOfMaterials = 1; pModel->materialList.push_back(textureInfo); } this->cleanUp(); return true; } /** \brief This function reads in all of the model's data, except the animation frames */ void MD2Loader::readMD2Data() { unsigned char buffer[MD2_MAX_FRAMESIZE]; this->pSkins = new tMd2Skin[this->header.numSkins]; this->pTexCoords = new tMd2TexCoord[this->header.numTexCoords]; this->pTriangles = new tMd2Face[this->header.numTriangles]; this->pFrames = new tMd2Frame[this->header.numFrames]; /* we read the skins */ fseek(this->pFile, this->header.offsetSkins, SEEK_SET); fread(this->pSkins, sizeof(tMd2Skin), this->header.numSkins, this->pFile); /* read all vertex data */ fseek(this->pFile, this->header.offsetTexCoords, SEEK_SET); fread(this->pTexCoords, sizeof(tMd2TexCoord), this->header.numTexCoords, this->pFile); /* read face data for each triangle (normals) */ fseek(this->pFile, this->header.offsetTriangles, SEEK_SET); fread(this->pTriangles, sizeof(tMd2Face), this->header.numTriangles, this->pFile); /* reading all frame data */ fseek(this->pFile, this->header.offsetFrames, SEEK_SET); for( int i = 0; i < this->header.numFrames; i++) { tMd2AliasFrame *pFrame = (tMd2AliasFrame *) buffer; this->pFrames[i].pVertices = new tMd2Triangle [this->header.numVertices]; /* read the frame animation data */ fread(pFrame, 1, this->header.frameSize, this->pFile); strcpy(this->pFrames[i].strName, pFrame->name); tMd2Triangle *pVertices = this->pFrames[i].pVertices; /* scale translations, store vertices: since id used a non-opengl xyz notation, we have to swap y and z and negate z axis */ for( int j = 0; j < this->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]; } } } /** \brief this function fills in the animation list for each animation by name and frame \param model */ void MD2Loader::parseAnimations(t3DModel *pModel) { tAnimationInfo animationInfo; string strLastName = ""; /* the animation parse process looks a little bit wired: this is because there are no fix bounds for the animation lengths, so the frames are destingushed using their names which is normaly composed of */ for(int i = 0; i < pModel->numOfObjects; i++) { string strName = this->pFrames[i].strName; int frameNum = 0; /* erease the frame number from the frame-name */ for(unsigned int j = 0; j < strName.length(); j++) { if( isdigit(strName[j]) && j >= strName.length() - 2) { frameNum = atoi(&strName[j]); strName.erase(j, strName.length() - j); break; } } printf("current: %s, last: %s\n", strName.c_str(), strLastName.c_str()); /* animations are sorted through their names: this is how its been done: */ if( strName != strLastName || i == pModel->numOfObjects - 1) { if( strLastName != "") { strcpy(animationInfo.strName, strLastName.c_str()); animationInfo.endFrame = i; pModel->animationList.push_back(animationInfo); memset(&animationInfo, 0, sizeof(tAnimationInfo)); pModel->numOfAnimations++; } animationInfo.startFrame = frameNum - 1 + i; } strLastName = strName; } } /** \brief this function converts the .md2 structures to our own model and object structures: decompress \param model */ void MD2Loader::convertDataStructures(t3DModel *pModel) { int j = 0, i = 0; memset(pModel, 0, sizeof(t3DModel)); pModel->numOfObjects = this->header.numFrames; this->parseAnimations(pModel); for (i = 0; i < pModel->numOfObjects; i++) { t3DObject currentFrame; currentFrame.numOfVerts = this->header.numVertices; currentFrame.numTexVertex = this->header.numTexCoords; currentFrame.numOfFaces = this->header.numTriangles; currentFrame.pVerts = new CVector3[currentFrame.numOfVerts]; for (j = 0; j < currentFrame.numOfVerts; j++) { currentFrame.pVerts[j].x = this->pFrames[i].pVertices[j].vertex[0]; currentFrame.pVerts[j].y = this->pFrames[i].pVertices[j].vertex[1]; currentFrame.pVerts[j].z = this->pFrames[i].pVertices[j].vertex[2]; } delete this->pFrames[i].pVertices; if( likely(i > 0)) { pModel->objectList.push_back(currentFrame); continue; } currentFrame.pTexVerts = new CVector2[currentFrame.numTexVertex]; currentFrame.pFaces = new tFace[currentFrame.numOfFaces]; for(j = 0; j < currentFrame.numTexVertex; j++) { currentFrame.pTexVerts[j].x = this->pTexCoords[j].u / float(this->header.skinWidth); currentFrame.pTexVerts[j].y = 1 - this->pTexCoords[j].v / float(this->header.skinHeight); } for(j = 0; j < currentFrame.numOfFaces; j++) { currentFrame.pFaces[j].vertIndex[0] = this->pTriangles[j].vertexIndices[0]; currentFrame.pFaces[j].vertIndex[1] = this->pTriangles[j].vertexIndices[1]; currentFrame.pFaces[j].vertIndex[2] = this->pTriangles[j].vertexIndices[2]; currentFrame.pFaces[j].coordIndex[0] = this->pTriangles[j].textureIndices[0]; currentFrame.pFaces[j].coordIndex[1] = this->pTriangles[j].textureIndices[1]; currentFrame.pFaces[j].coordIndex[2] = this->pTriangles[j].textureIndices[2]; } pModel->objectList.push_back(currentFrame); } } /** \brief this function conputes the normals of the model \param model */ void MD2Loader::computeNormals(t3DModel *pModel) { CVector3 vVector1, vVector2, vNormal, vPoly[3]; if( unlikely(pModel->numOfObjects <= 0)) return; /* now computing face normals: this means just averaging the vertex normals of a face */ /* so for every object: */ for(int index = 0; index < pModel->numOfObjects; index++) { t3DObject *pObject = &(pModel->objectList[index]); /* 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]; for(int i=0; i < pObject->numOfFaces; i++) { /* cache the points to make coding easier :) */ 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]]; vVector1 = MathHelp::VectorDiff(vPoly[0], vPoly[2]); vVector2 = MathHelp::VectorDiff(vPoly[2], vPoly[1]); vNormal = MathHelp::CrossProduct(vVector1, vVector2); pTempNormals[i] = vNormal; vNormal = MathHelp::NormalizeVector(vNormal); pNormals[i] = vNormal; } /* now calculating vertex normals */ CVector3 vSum = {0.0, 0.0, 0.0}; CVector3 vZero = vSum; int shared=0; for (int i = 0; i < pObject->numOfVerts; i++) { for (int j = 0; j < pObject->numOfFaces; j++) { if (pObject->pFaces[j].vertIndex[0] == i || pObject->pFaces[j].vertIndex[1] == i || pObject->pFaces[j].vertIndex[2] == i) { vSum = MathHelp::AddVector(vSum, pTempNormals[j]); shared++; } } pObject->pNormals[i] = MathHelp::DivideVectorByScaler(vSum, float(-shared)); pObject->pNormals[i] = MathHelp::NormalizeVector(pObject->pNormals[i]); vSum = vZero; shared = 0; } delete [] pTempNormals; delete [] pNormals; } } /** \brief This function cleans up our allocated memory and closes the file */ void MD2Loader::cleanUp() { fclose(this->pFile); if( this->pSkins) delete [] this->pSkins; if( this->pTexCoords) delete this->pTexCoords; if( this->pTriangles) delete this->pTriangles; if( this->pFrames) delete this->pFrames; } /******************************************************************************** * MD2LOADER2 * ********************************************************************************/ MD2Model2::MD2Model2() { this->pVertices = NULL; this->pGLCommands = NULL; this->pLightNormals = NULL; this->numFrames = 0; this->numVertices = 0; this->numGLCommands = 0; this->textureID = 0; this->scaleFactor = 1.0f; //this->setAnim(0); } MD2Model2::~MD2Model2() { delete [] this->pVertices; delete [] this->pGLCommands; delete [] this->pLightNormals; } bool MD2Model2::loadModel(const char *fileName) { FILE *pFile; //file stream tMd2Header header; char* buffer; //buffer for frame data sFrame* frame; //temp frame sVec3D *pVertex; int* pNormals; pFile = fopen(fileName, "rb"); if( unlikely(!pFile)) { PRINTF(1)("Couldn't open the MD2 File for loading. Exiting.\n"); return false; } fread(&header, 1, sizeof(tMd2Header), pFile); /* check for the header version: make sure its a md2 file :) */ if( unlikely(header.version != MD2_VERSION) && unlikely(header.ident != MD2_IDENT)) { PRINTF(1)("Couldn't load file %s: invalid file format: stop loading\n", fileName); return false; } /* got the data: map it to locals */ this->numFrames = header.numFrames; this->numVertices = header.numVertices; this->numGLCommands = header.numGlCommands; /* allocate memory for the data storage */ this->pVertices = new sVec3D[this->numVertices * this->numFrames]; this->pGLCommands = new int[this->numGLCommands]; this->pLightNormals = new int[this->numVertices * this->numFrames]; buffer = new char[this->numFrames * header.frameSize]; /* read frame data from the file to a temp buffer */ fseek(pFile, header.offsetFrames, SEEK_SET); fread(buffer, header.frameSize, this->numFrames, pFile); /* read opengl commands */ fseek(pFile, header.offsetGlCommands, SEEK_SET); fread(this->pGLCommands, sizeof(int), this->numGLCommands, pFile); for(int i = 0; i < this->numFrames; ++i) { frame = (sFrame*)(buffer + header.frameSize * i); pVertex = this->pVertices + this->numVertices * i; pNormals = this->pLightNormals + this->numVertices * i; for(int j = 0; j < this->numVertices; ++j) { /* SPEEDUP: *(pVerts + i + 0) = (*(frame->pVertices + i + 0)... */ pVertex[i][0] = (frame->pVertices[i].v[0] * frame->scale[0]) + frame->translate[0]; pVertex[i][1] = (frame->pVertices[i].v[1] * frame->scale[1]) + frame->translate[1]; pVertex[i][2] = (frame->pVertices[i].v[2] * frame->scale[2]) + frame->translate[2]; pNormals[i] = frame->pVertices[i].lightNormalIndex; } } delete [] buffer; fclose(pFile); } bool MD2Model2::loadSkin(const char* fileName) { this->material = new Material("md2ModelTest"); this->material->setDiffuseMap(fileName); this->material->setIllum(3); this->material->setAmbient(1.0, 1.0, 1.0); } void MD2Model2::draw() { glPushMatrix(); /* rotate because id software uses another orientation then the openGL default */ /* \todo: rotate the axis already when loading... easy :) */ glRotatef(-90.0, 1.0, 0.0, 0.0); glRotatef(-90.0, 0.0, 0.0, 1.0); this->renderFrame(); glPopMatrix(); } /** \brief initializes an array of vert with the current frame scaled vertices we won't use the pVertices array directly, since its much easier and we need saving of data anyway */ void MD2Model2::interpolate(sVec3D* verticesList) { sVec3D* currVec; sVec3D* nextVec; currVec = &this->pVertices[this->numVertices * this->animationState.currentFrame]; nextVec = &this->pVertices[this->numVertices * this->animationState.currentFrame]; for(int i = 0; i < this->numFrames; ++i) { /* verticesList[i][0] = this->pVertices[i + (this->numFrames * this->animationState.currentFrame)][0] * this->scaleFactor; verticesList[i][1] = this->pVertices[i + (this->numFrames * this->animationState.currentFrame)][1] * this->scaleFactor; verticesList[i][2] = this->pVertices[i + (this->numFrames * this->animationState.currentFrame)][2] * this->scaleFactor; */ verticesList[i][0] = (currVec[i][0] + this->animationState.interpolationState * (nextVec[i][0] - currVec[i][0])) * this->scaleFactor; verticesList[i][1] = (currVec[i][1] + this->animationState.interpolationState * (nextVec[i][1] - currVec[i][1])) * this->scaleFactor; verticesList[i][2] = (currVec[i][2] + this->animationState.interpolationState * (nextVec[i][2] - currVec[i][2])) * this->scaleFactor; } } void MD2Model2::setAnim(int type) { if( (type < 0) || (type > MAX_ANIMATIONS) ) type = 0; this->animationState.startFrame = animationList[type].firstFrame; this->animationState.endFrame = animationList[type].lastFrame; this->animationState.nextFrame = animationList[type].firstFrame + 1; this->animationState.fps = animationList[type].fps; this->animationState.type = type; } void MD2Model2::animate(float time) { this->animationState.localTime += time; if( this->animationState.localTime - this->animationState.lastTime > (1.0f / this->animationState.fps)) { this->animationState.currentFrame = this->animationState.nextFrame; this->animationState.nextFrame++; if( this->animationState.nextFrame > this->animationState.endFrame) this->animationState.nextFrame = this->animationState.startFrame; this->animationState.lastTime = this->animationState.localTime; } if( this->animationState.currentFrame > (this->numFrames - 1) ) this->animationState.currentFrame = 0; if( this->animationState.nextFrame > (this->numFrames - 1) ) this->animationState.nextFrame = 0; this->animationState.interpolationState = this->animationState.fps * (this->animationState.localTime - this->animationState.lastTime); } /* hhmmm... id used a very different way to do lightning...*/ void MD2Model2::processLighting() { shadeDots = anormsDots[((int)(md2Angle*(SHADEDOT_QUANT / 360.0)))&(SHADEDOT_QUANT - 1)]; } void MD2Model2::renderFrame() { static sVec3D verticesList[MD2_MAX_VERTICES]; /* performance: created only once in a lifetime */ int* pCommands = this->pGLCommands; /* some face culling stuff */ //glPushAttrib(GL_POLYGON_BIT); //glFrontFace(GL_CW); //glEnable(GL_CULL_FACE); //glCullFace(GL_BACK); this->processLighting(); //this->interpolate(verticesList); this->material->select(); /* draw the triangles */ /* \todo: take int i out of while loop */ while( int i = *(pCommands++)) /* strange looking while loop for maximum performance */ { if( i < 0) { glBegin(GL_TRIANGLE_FAN); i = -i; } else { glBegin(GL_TRIANGLE_STRIP); } for(; i > 0; --i, pCommands += 3) /* down counting for loop, next 3 gl commands */ { /* for quake2 lighting */ float l = shadeDots[this->pLightNormals[pCommands[2]]]; //glColor3f(l * lcolor[0], l * lcolor[1], l * lcolor[2]); glTexCoord2f( ((float *)pCommands)[0], ((float *)pCommands)[1] ); glNormal3fv(anorms[this->pLightNormals[2]]); glVertex3fv(verticesList[pCommands[2]]); } glEnd(); } //glDisable(GL_CULL_FACE); //glPopAttrib(); }