/* Copyright (C) 2010 Matthew Baranowski, Sander van Rossen & Raven software. 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 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "system.h" #include "ndictionary.h" #include "md3gl.h" #include "md3view.h" #include "DiskIO.h" #include "oddbits.h" #include "matcomp.h" #include // for offsetof macro #include "mdx_format.h" #include "g2export.h" #include "matrix4.h" #pragma warning (disable : 4786) //ident trunc #include using namespace std; // if enabled, this'll make the g2 model have same orientation as MD3, // if disabled, the model will have all that weird 90-degree bollocks applied to it. // //#define PERFECT_CONVERSION // this is now runtime, controlled by "bool g_bPerfect" extern bool g_bPerfect; #ifndef M_PI #define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h #endif #define DEG2RAD( a ) ( ( (a) * M_PI ) / 180.0F ) #define RAD2DEG( a ) ( ( (a) * 180.0f ) / M_PI ) /* ** NormalToLatLong ** ** We use two byte encoded normals in some space critical applications. ** Lat = 0 at (1,0,0) to 360 (-1,0,0), encoded in 8-bit sine table format ** Lng = 0 at (0,0,1) to 180 (0,0,-1), encoded in 8-bit sine table format ** */ void NormalToLatLong( const vec3_t normal, byte bytes[2] ) { // check for singularities if ( normal[0] == 0 && normal[1] == 0 ) { if ( normal[2] > 0 ) { bytes[0] = 0; bytes[1] = 0; // lat = 0, long = 0 } else { bytes[0] = 128; bytes[1] = 0; // lat = 0, long = 128 } } else { int a, b; a = RAD2DEG( atan2( normal[1], normal[0] ) ) * (255.0f / 360.0f ); a &= 0xff; b = RAD2DEG( acos( normal[2] ) ) * ( 255.0f / 360.0f ); b &= 0xff; bytes[0] = b; // longitude bytes[1] = a; // lattitude } } double VectorLength( const vec3_t v ) { double length; length = sqrt (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); return length; } void ClearBounds (vec3_t mins, vec3_t maxs) { mins[0] = mins[1] = mins[2] = 99999; maxs[0] = maxs[1] = maxs[2] = -99999; } void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ) { int i; vec_t val; for (i=0 ; i<3 ; i++) { val = v[i]; if (val < mins[i]) mins[i] = val; if (val > maxs[i]) maxs[i] = val; } } #define OUTPOS() //OutputDebugString(va("0x%X\n",filesize(fHandle))) list FilesWritten; bool bAutoOverWrite = false; bool bAskThem = true; gl_model* gpModel=NULL; bool ExportThisModel( LPCSTR psFilename, gl_model* pModel, bool bExportAsMD3) { bool bG2MultiLODExportHasErrors = false; bool bG2ForceSingleLODExport = false; // this compiler is being really gay and insisting I move these up here to avoid their initialising being // skipped by the first 'goto' commands, even though they're not used later. Duh!!! // int iOutLODCount = 1; int iOutLOD; gpModel = pModel; // so ghoul2 exporter code can get at the model data // OutputDebugString(va("#### Exporting: %s\n",psFilename)); if (!pModel) // call with NULL pModel to display info on stuff written { string str; str = "Files written:\n\n"; list ::iterator it; STL_ITERATE(it,FilesWritten) { str += (*it).c_str(); str += "\n"; } str += "\n\nNo errors"; InfoBox(str.c_str()); return true; } StartWait(); FILE *fHandle = NULL; bool bOneFrameMD3ExportHack = false; if (!bExportAsMD3) // and hence Ghoul2... { if (pModel->modelType != MODTYPE_MD3) { ErrorBox("Error! Currently, this program will only export loaded MD3's as Ghoul2 models"); goto error; } // and if it's an MD3, then it must only be one frame since there's no skeletal information... // if (pModel->iNumFrames != 1) { if (!GetYesNo( va("Only MD3 models with 1 frame can be exported as Ghoul2, this model has %d frames!\n\nExport just the first frame? ( 'NO' will abort export )",pModel->iNumFrames))) { goto error; } bOneFrameMD3ExportHack = true; } // because of multiple-LOD-from-multiple-model issues I now need to pre-scan some stuff to check for // mismatching surfaces etc... // if (pModel->pModel_LOD1 || pModel->pModel_LOD2) { // multiple models loaded, need to run checks... // char sExportSingleLODPrompt[4096]="Unknown";//{0}; // bool bMisMatchingSurfacenameQuestionAlreadyAsked = false; int iLOD; for (iLOD=0; iLOD<3; iLOD++) { gl_model* pMod = NULL; switch (iLOD) { //case 0: pMod = pModel; break; case 1: pMod = pModel->pModel_LOD1; break; case 2: pMod = pModel->pModel_LOD2; break; } if (pMod) { if (pModel->modelType != MODTYPE_MD3) { sprintf(sExportSingleLODPrompt,"LOD %d is not an MD3 model",iLOD); bG2MultiLODExportHasErrors = true; break; } if (pMod->iNumMeshes > pModel->iNumMeshes) { sprintf(sExportSingleLODPrompt,"LOD %d has more surfaces than LOD 0",iLOD); bG2MultiLODExportHasErrors = true; break; } if (pMod->iNumMeshes < pModel->iNumMeshes) { // this is ok because I'll generate fake surfaces, but let's just see if the surfaces // so far match, and if not, issue a warning... // if (!bMisMatchingSurfacenameQuestionAlreadyAsked) { for (int iMesh=0; iMesh<(int)pMod->iNumMeshes; iMesh++) { LPCSTR pLOD0MeshName = pModel->pMeshes[iMesh].sName; LPCSTR pLODXMeshName = pModel->pMeshes[iMesh].sName; if (stricmp(pLOD0MeshName,pLODXMeshName)) { bG2ForceSingleLODExport = !GetYesNo(va("Multi-LOD-export issue:\n\nLOD %d has less surfaces than LOD 0 (which isn't a problem, I can insert extra dummy surfaces), but the common surface names also mismatch, surface %d is called \"%s\" in this LOD, and \"%s\" in the original\n\nDo you still want to go ahead and export as multi-LOD?\n\n('NO' will export single-LOD only)", iLOD, iMesh, pLODXMeshName, pLOD0MeshName)); bMisMatchingSurfacenameQuestionAlreadyAsked = true; break; } } } } } } // Ok, do we need to offer them a prompt to export single-LOD only? // if (iLOD != 3) { if (bG2MultiLODExportHasErrors) { bG2ForceSingleLODExport = GetYesNo(va("Multi-LOD-export failure:\n\n%s\n\nDo you want to go ahead and export as a single-LOD model?",sExportSingleLODPrompt)); if (!bG2ForceSingleLODExport) // since we CANNOT export multi-lods because of the errtype, exit if they say 'no'. goto error; } } } } if (pModel == mdview.baseModel) { // OutputDebugString("!!!! First model\n"); // the only model, or the first in a sequence if exporting several... // FilesWritten.clear(); if (bAskThem) { //bAskThem = true; bAutoOverWrite = false; } } switch (pModel->modelType) // type of current model, not export format { case MODTYPE_MD3: iOutLODCount = 1; break; case MODTYPE_MDR: iOutLODCount = pModel->Q3ModelStuff.md4->numLODs; break; default: ErrorBox("casedefault: Unhandled model type"); goto error; } char sFilename[MAX_PATH]; for (iOutLOD=0; iOutLODiNumFrames; Header.iNumTags = pModel->iNumTags; Header.iMaxSkinNum = 0; // ? (always seems to be 0 in models I've tried) Header.iHeadSize = sizeof(Header); Header.iOffsetToTags = sizeof(Header) + (Header.iNumFrames * sizeof md3BoundFrame_t); Header.iOffsetToMeshes = Header.iOffsetToTags + (Header.iNumFrames * Header.iNumTags * sizeof(md3Tag_t)/*BoneFrame_t*/); Header.iFileSize = NULL; md4LOD_t *pLOD = NULL; switch (pModel->modelType) { case MODTYPE_MD3: Header.iNumMeshes = pModel->iNumMeshes; break; case MODTYPE_MDR: { pLOD = (md4LOD_t *) ((byte *)pModel->Q3ModelStuff.md4 + pModel->Q3ModelStuff.md4->ofsLODs); for ( int i=0; iofsEnd ); } Header.iNumMeshes = pLOD->numSurfaces; break; } default: ErrorBox("casedefault: Unhandled model type"); goto error; } fwrite(&Header,sizeof(Header),1,fHandle); OUTPOS(); // write the boundary frames... // md3BoundFrame_t *pBoundFrames = NULL; // MDRs will have to come back and fill this in later during freeze long lBoundFrameWriteSeekPos = ftell(fHandle); int iFrame = 0; for (iFrame = 0; iFramemodelType) { case MODTYPE_MD3: // just write out the original one from when the model was loaded in... // BoundFrame = pModel->pMD3BoundFrames[iFrame]; break; case MODTYPE_MDR: if (iFrame==0) pBoundFrames = new md3BoundFrame_t[Header.iNumFrames]; // and setup this entry ready for freeze-code boundary-determiner later... // ClearBounds(pBoundFrames[iFrame].bounds[0], pBoundFrames[iFrame].bounds[1]); // for the moment, just write out zeroes... // memset(&BoundFrame,0,sizeof(BoundFrame)); break; default: ErrorBox("casedefault: Unhandled model type"); goto error; } fwrite(&BoundFrame, sizeof(BoundFrame),1,fHandle); } OUTPOS(); // write tags... (MDR already uses MD3 tags in this app) // for (iFrame = 0; iFrametags[iFrame][iTag]; fwrite(pTag, sizeof(*pTag),1,fHandle); } } OUTPOS(); // write meshes... // for (int iMesh = 0; iMeshmodelType) { case MODTYPE_MD3: pMesh = &pModel->pMeshes[ iMesh ]; strcpy(MeshHeader.sName, pMesh->sName); MeshHeader.iNumMeshFrames = bOneFrameMD3ExportHack ? 1 : pMesh->iNumMeshFrames; // same as global count, but convenient MeshHeader.iNumSkins = pMesh->iNumSkins; MeshHeader.iNumVertices = pMesh->iNumVertices; MeshHeader.iNumTriangles = pMesh->iNumTriangles; break; case MODTYPE_MDR: { pSurface = (md4Surface_t *)( (byte *)pLOD + pLOD->ofsSurfaces ); for (int iSurf=0; iSurfofsEnd ); } strcpy(MeshHeader.sName, pSurface->name); MeshHeader.iNumMeshFrames = pModel->iNumFrames; // same as global count, but convenient MeshHeader.iNumSkins = 1; MeshHeader.iNumVertices = pSurface->numVerts; MeshHeader.iNumTriangles = pSurface->numTriangles; break; } default: ErrorBox("casedefault: Unhandled model type"); goto error; } typedef struct { short x,y,z,normal; }MD3MeshVert; MeshHeader.iOffsetToTriangles = sizeof(MeshHeader) + MeshHeader.iNumSkins * sizeof(MeshHeader.sName); MeshHeader.iHeadSize = sizeof(MeshHeader); MeshHeader.iOffsetToTexVecs = MeshHeader.iOffsetToTriangles + (MeshHeader.iNumTriangles * sizeof(TriVec)); MeshHeader.iOffsetToVertices = MeshHeader.iOffsetToTexVecs + (MeshHeader.iNumVertices * sizeof(TexVec)); MeshHeader.iMeshSize = MeshHeader.iOffsetToVertices + (MeshHeader.iNumVertices * MeshHeader.iNumMeshFrames * sizeof(MD3MeshVert)); fwrite(&MeshHeader,sizeof(MeshHeader),1,fHandle); OUTPOS(); // this is one of these leftover crap things, AFAIK it's always 1, that's all that's stored anyway, so... // for (int iSkin=0; iSkinmodelType) { case MODTYPE_MD3: strcpy(sName, pMesh->sTextureName); break; case MODTYPE_MDR: strcpy(sName, pSurface->shader); break; default: ErrorBox("casedefault: Unhandled model type"); goto error; } fwrite(sName,sizeof(sName),1,fHandle); } OUTPOS(); shaderCommands_t* pTess = NULL; // write triangles... // for (int iTri=0; iTrimodelType) { case MODTYPE_MD3: memcpy(Tri,pMesh->triangles[iTri],sizeof(Tri)); // stupid compiler won't do an assign break; case MODTYPE_MDR: pTess = Freeze_Surface( pSurface, 0 ); // frame 0 is fine for working out tri indexes and (next) tex coords memcpy(Tri,&pTess->indexes[iTri*3],sizeof(Tri)); break; default: ErrorBox("casedefault: Unhandled model type"); goto error; } put32(Tri[0],fHandle); put32(Tri[1],fHandle); put32(Tri[2],fHandle); } OUTPOS(); // write tex coords... // for (int iVert=0; iVertmodelType) { case MODTYPE_MD3: u = pMesh->textureCoord[iVert][0]; v = pMesh->textureCoord[iVert][1]; break; case MODTYPE_MDR: assert(pTess); u = pTess->texCoords[iVert][0][0]; v = pTess->texCoords[iVert][0][1]; break; default: ErrorBox("casedefault: Unhandled model type"); goto error; } putFloat(u,fHandle); putFloat(v,fHandle); } OUTPOS(); // actual mesh data... // for (int iFrame=0; iFramemodelType) { case MODTYPE_MD3: x = (short)((pMesh->meshFrames[iFrame].pVerts[iVert][0])*64); y = (short)((pMesh->meshFrames[iFrame].pVerts[iVert][1])*64); z = (short)((pMesh->meshFrames[iFrame].pVerts[iVert][2])*64); normal = pMesh->meshFrames[iFrame].pNormalIndexes[iVert]; break; case MODTYPE_MDR: if (iVert==0) pTess = Freeze_Surface( pSurface, iFrame ); x = (short)((pTess->xyz[iVert][0])*64); y = (short)((pTess->xyz[iVert][1])*64); z = (short)((pTess->xyz[iVert][2])*64); byte thing[2]; NormalToLatLong( pTess->normal[iVert], thing ); normal = *(short*)thing; /////xxxxxxxxxxxxx this needs testing in-game // extra stuff needed for MDR source, we need to fill in the stuff about boundary frames // here, which MD3 didn't need to do because it just writes out whatever it found when // the model was loaded in... // assert(pBoundFrames); if (pBoundFrames) { AddPointToBounds (pTess->xyz[iVert], pBoundFrames[iFrame].bounds[0], pBoundFrames[iFrame].bounds[1]); } break; default: ErrorBox("casedefault: Unhandled model type"); goto error; } put16(x,fHandle); put16(y,fHandle); put16(z,fHandle); put16(normal,fHandle); } } OUTPOS(); } // if we were writing from an MDR source, we need to go back and write out the newly-generated boundary frames // (writing from MD3s will have used the frames found during original load) // if (pBoundFrames) { // do some final processing... // for (int iFrame=0; iFrame maxRadius ) maxRadius = VectorLength( tmpVec ); } pBoundFrames[iFrame].radius = maxRadius; } // now write all boundframes out... // fseek(fHandle,lBoundFrameWriteSeekPos,SEEK_SET); fwrite(pBoundFrames,sizeof(md3BoundFrame_t),Header.iNumFrames,fHandle); delete [] pBoundFrames; pBoundFrames = NULL; } // now go back and write in the file size... // fseek(fHandle,offsetof(MD3Head,iFileSize), SEEK_SET); put32(filesize(fHandle),fHandle); fclose(fHandle); } else { ErrorBox(va("Error: Unable to write to '%s'!\n",sFilename)); } } else { // exporting an MD3 model as a ghoul2 model, so we first need to ask how many LODs there are from the special LOD-models loaded, // even though they aren't part of the same MD3 model... // int iActualNumLODs = iOutLODCount; if (pModel->pModel_LOD1) iActualNumLODs++; if (pModel->pModel_LOD2) iActualNumLODs++; LPCSTR psNameGLA = NULL; iActualNumLODs = bG2ForceSingleLODExport?1:iActualNumLODs; char *psErrMess = ExportGhoul2FromMD3(sFilename, iActualNumLODs, pModel->iNumMeshes, pModel->iNumTags, &psNameGLA ); if (!psErrMess) { FilesWritten.push_back(sFilename); FilesWritten.push_back(psNameGLA); } else { ErrorBox(psErrMess); } } } } EndWait(); return true; error: EndWait(); if (fHandle) { fclose(fHandle); fHandle = NULL; list ::iterator it; STL_ITERATE(it,FilesWritten) { remove((*it).c_str()); } } return false; } // ghoul2 exported interface functions... // LPCSTR G2Exporter_Surface_GetName(int iSurfaceIndex) { // always assume LOD0 here, so ignore other loaded MD3 models... // switch (gpModel->modelType) { case MODTYPE_MD3: { if (iSurfaceIndex < (int)gpModel->iNumMeshes) { // standard surface... // gl_mesh *pMesh =&gpModel->pMeshes[iSurfaceIndex]; return pMesh->sName; } else { // tag surface... // static char sTemp[1024]; Tag* pTag = &gpModel->tags[0][iSurfaceIndex - gpModel->iNumMeshes]; // // prepend name with a '*', and remove "tag_" from name start if present... // strcpy(sTemp,"*"); strcat(sTemp, (!strnicmp(pTag->Name,"tag_",4)) ? &pTag->Name[4] : pTag->Name); return sTemp; } } default: { static bool bAlreadyReported = false; if (!bAlreadyReported) { bAlreadyReported = true; assert(0); ErrorBox(va("G2Exporter_Surface_GetName(): Error! modeltype %d has no case handler!\n",gpModel->modelType)); } } break; } return "Error"; } LPCSTR G2Exporter_Surface_GetShaderName(int iSurfaceIndex) { // always assume LOD0 here, so ignore other loaded MD3 models... // switch (gpModel->modelType) { case MODTYPE_MD3: { if (iSurfaceIndex < (int)gpModel->iNumMeshes) { // standard surface... // gl_mesh *pMesh =&gpModel->pMeshes[iSurfaceIndex]; return pMesh->sTextureName; } else { // tag surface... // return "[NoMaterial]"; } } default: { static bool bAlreadyReported = false; if (!bAlreadyReported) { bAlreadyReported = true; assert(0); ErrorBox(va("G2Exporter_Surface_GetShaderName(): Error! modeltype %d has no case handler!\n",gpModel->modelType)); } } break; } return "Error"; } static gl_model *Model_AccountForLOD(gl_model* pModel, int iLOD) { gl_model *pMod = pModel; switch (iLOD) { case 0: break; case 1: pMod = pMod->pModel_LOD1?pMod->pModel_LOD1:pMod; break; case 2: pMod = pMod->pModel_LOD2?pMod->pModel_LOD2:pMod; break; default: assert(0); } return pMod; } int G2Exporter_Surface_GetNumVerts(int iSurfaceIndex, int iLOD) { gl_model* pModel = Model_AccountForLOD(gpModel, iLOD); switch (pModel->modelType) { case MODTYPE_MD3: { if (iSurfaceIndex < (int)gpModel->iNumMeshes) // gpModel, not pModel, to cope with models of differing # meshes { if (iSurfaceIndex < (int)pModel->iNumMeshes) // now check surface is legal for THIS LOD model... { // standard surface... // gl_mesh *pMesh =&pModel->pMeshes[iSurfaceIndex]; return pMesh->iNumVertices; } else { // surface was a legal index for LOD 0, but not for this one, so return fake surface info... // return 1; } } else { // tag surface... // return 3; // for one triangle } } default: { static bool bAlreadyReported = false; if (!bAlreadyReported) { bAlreadyReported = true; assert(0); ErrorBox(va("G2Exporter_Surface_GetNumVerts(): Error! modeltype %d has no case handler!\n",pModel->modelType)); } } break; } return 0; } int G2Exporter_Surface_GetNumTris(int iSurfaceIndex, int iLOD) { gl_model* pModel = Model_AccountForLOD(gpModel, iLOD); switch (pModel->modelType) { case MODTYPE_MD3: { if (iSurfaceIndex < (int)gpModel->iNumMeshes) // gpModel, not pModel, to cope with models of differing # meshes { if (iSurfaceIndex < (int)pModel->iNumMeshes) // now check surface is legal for THIS LOD model... { // standard surface... // gl_mesh *pMesh =&pModel->pMeshes[iSurfaceIndex]; return pMesh->iNumTriangles; } else { // surface was a legal index for LOD 0, but not for this one, so return fake surface info... // return 1; } } else { // tag surface... // return 1; // for one triangle } } default: { static bool bAlreadyReported = false; if (!bAlreadyReported) { bAlreadyReported = true; assert(0); ErrorBox(va("G2Exporter_Surface_GetNumTris(): Error! modeltype %d has no case handler!\n",pModel->modelType)); } } break; } return 0; } int G2Exporter_Surface_GetTriIndex(int iSurfaceIndex, int iTriangleIndex, int iTriVert, int iLOD) { gl_model* pModel = Model_AccountForLOD(gpModel, iLOD); switch (pModel->modelType) { case MODTYPE_MD3: { if (iSurfaceIndex < (int)gpModel->iNumMeshes) // gpModel, not pModel, to cope with models of differing # meshes { if (iSurfaceIndex < (int)pModel->iNumMeshes) // now check surface is legal for THIS LOD model... { // standard surface... // gl_mesh *pMesh = &pModel->pMeshes[iSurfaceIndex]; TriVec *pTriIndexes= &pMesh->triangles[iTriangleIndex]; return pTriIndexes[0][iTriVert]; } else { // surface was a legal index for LOD 0, but not for this one, so return fake surface info... // return 0; // only one vert in a fake surface, so index is always zero } } else { // tag surface... // return iTriVert; } } default: { static bool bAlreadyReported = false; if (!bAlreadyReported) { bAlreadyReported = true; assert(0); ErrorBox(va("G2Exporter_Surface_GetTriIndex(): Error! modeltype %d has no case handler!\n",pModel->modelType)); } } break; } return 0; } ////////////////////////////////////////////// // // Some crap to get at Quake's weird tables simply to determine vert normals for MD3s... // #define FUNCTABLE_SIZE 1024 #define FUNCTABLE_SIZE2 10 #define FUNCTABLE_MASK (FUNCTABLE_SIZE-1) float sinTable[FUNCTABLE_SIZE]; //float squareTable[FUNCTABLE_SIZE]; //float triangleTable[FUNCTABLE_SIZE]; //float sawToothTable[FUNCTABLE_SIZE]; //float inverseSawToothTable[FUNCTABLE_SIZE]; static void EnsureQuakeTablesSetup(void) { static bool bDone = false; if (!bDone) { bDone = true; for ( int i = 0; i < FUNCTABLE_SIZE; i++ ) { sinTable[i] = sin( DEG2RAD( i * 360.0f / ( ( float ) ( FUNCTABLE_SIZE - 1 ) ) ) ); // squareTable[i] = ( i < FUNCTABLE_SIZE/2 ) ? 1.0f : -1.0f; // sawToothTable[i] = (float)i / FUNCTABLE_SIZE; // inverseSawToothTable[i] = 1.0 - tr.sawToothTable[i]; // // if ( i < FUNCTABLE_SIZE / 2 ) // { // if ( i < FUNCTABLE_SIZE / 4 ) // { // tr.triangleTable[i] = ( float ) i / ( FUNCTABLE_SIZE / 4 ); // } // else // { // tr.triangleTable[i] = 1.0f - tr.triangleTable[i-FUNCTABLE_SIZE / 4]; // } // } // else // { // tr.triangleTable[i] = -tr.triangleTable[i-FUNCTABLE_SIZE/2]; // } } } } ////////////////////////////////////////////// vec3_t *G2Exporter_Surface_GetVertNormal(int iSurfaceIndex, int iVertIndex, int iLOD) { gl_model* pModel = Model_AccountForLOD(gpModel, iLOD); static vec3_t v3={0}; switch (pModel->modelType) { case MODTYPE_MD3: { if (iSurfaceIndex < (int)gpModel->iNumMeshes) // gpModel, not pModel, to cope with models of differing # meshes { if (iSurfaceIndex < (int)pModel->iNumMeshes) // now check surface is legal for THIS LOD model... { // standard surface... // gl_mesh *pMesh = &pModel->pMeshes[iSurfaceIndex]; short *pNormalIndexes = pMesh->meshFrames[0].pNormalIndexes; EnsureQuakeTablesSetup(); // heh, well-tacky. unsigned int lat, lng; lat = ( pNormalIndexes[iVertIndex] >> 8 ) & 0xff; lng = ( pNormalIndexes[iVertIndex] & 0xff ); lat *= (FUNCTABLE_SIZE/256); lng *= (FUNCTABLE_SIZE/256); // decode X as cos( lat ) * sin( long ) // decode Y as sin( lat ) * sin( long ) // decode Z as cos( long ) v3[0] = sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * sinTable[lng]; v3[1] = sinTable[lat] * sinTable[lng]; v3[2] = sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; if ( g_bPerfect ) { return &v3; } else { // 90-degree skew thing... // Matrix4 Swap; Swap.Identity(); if (1) { Swap.SetRow(0,Vect3(0.0f,-1.0f,0.0f)); Swap.SetRow(1,Vect3(1.0f,0.0f,0.0f)); } Swap.CalcFlags(); Vect3 v3In((const float *)v3); static Vect3 v3Out; Swap.XFormVect(v3Out,v3In); return (vec3_t*) &v3Out; } } else { // surface was a legal index for LOD 0, but not for this one, so return fake surface info... // memset(v3,0,sizeof(v3)); return &v3; } } else { // tag surface... // // I don't think tag-surfaces normals have any meaning for ghoul2, so... // //TagFrame *pTag = &pModel->tags[0][iSurfaceIndex - pModel->iNumMeshes]; memset(v3,0,sizeof(v3)); return &v3; } } default: { static bool bAlreadyReported = false; if (!bAlreadyReported) { bAlreadyReported = true; assert(0); ErrorBox(va("G2Exporter_Surface_GetVertNormal(): Error! modeltype %d has no case handler!\n",pModel->modelType)); } } break; } return &v3; } vec3_t *G2Exporter_Surface_GetVertCoords(int iSurfaceIndex, int iVertIndex, int iLOD) { gl_model* pModel = Model_AccountForLOD(gpModel, iLOD); static vec3_t v3={0}; switch (pModel->modelType) { case MODTYPE_MD3: { if (iSurfaceIndex < (int)gpModel->iNumMeshes) // gpModel, not pModel, to cope with models of differing # meshes { if (iSurfaceIndex < (int)pModel->iNumMeshes) // now check surface is legal for THIS LOD model... { // standard surface... // gl_mesh *pMesh = &pModel->pMeshes[iSurfaceIndex]; Vec3 *pVerts = &pMesh->meshFrames[0].pVerts[iVertIndex]; if ( g_bPerfect ) { return pVerts; } else { // 90-degree skew thing... // Matrix4 Swap; Swap.Identity(); if (1) { Swap.SetRow(0,Vect3(0.0f,-1.0f,0.0f)); Swap.SetRow(1,Vect3(1.0f,0.0f,0.0f)); } Swap.CalcFlags(); Vect3 v3In((const float *)pVerts); static Vect3 v3Out; Swap.XFormVect(v3Out,v3In); return (vec3_t*) &v3Out; } } else { // surface was a legal index for LOD 0, but not for this one, so return fake surface info... // memset(v3,0,sizeof(v3)); return &v3; } } else { // tag surface... // assert(iVertIndex<3); Tag* pTag = &pModel->tags[0][iSurfaceIndex - pModel->iNumMeshes]; vec3_t v3New; if ( g_bPerfect ) { v3New[0] = pTag->axis[0][iVertIndex] ; v3New[1] = pTag->axis[1][iVertIndex] ; v3New[2] = pTag->axis[2][iVertIndex] ; // don't worry about how this crap works, it just does (arrived at by empirical methods... :-) // // (mega-thanks to Gil as usual) // if (iVertIndex==0) { VectorCopy(pTag->origin,v3); } else if (iVertIndex==1) { v3New[0] = pTag->axis[0][iG2_TRISIDE_MIDDLE]; v3New[1] = pTag->axis[1][iG2_TRISIDE_MIDDLE]; v3New[2] = pTag->axis[2][iG2_TRISIDE_MIDDLE]; VectorAdd(pTag->origin,v3New,v3); } else { v3New[0] = pTag->axis[0][iG2_TRISIDE_LONGEST]; v3New[1] = pTag->axis[1][iG2_TRISIDE_LONGEST]; v3New[2] = pTag->axis[2][iG2_TRISIDE_LONGEST]; VectorSubtract(pTag->origin,v3New,v3); } return &v3; } else { // 90-degree skew thing... // v3New[0] = pTag->axis[0][iVertIndex] ; v3New[1] = pTag->axis[1][iVertIndex] ; v3New[2] = pTag->axis[2][iVertIndex] ; // don't worry about how this crap works, it just does (arrived at by empirical methods... :-) // // (mega-thanks to Gil as usual) // if (iVertIndex==2) { VectorCopy(pTag->origin,v3); } else if (iVertIndex==1) { v3New[0] = 2.0f * pTag->axis[1][iG2_TRISIDE_MIDDLE]; v3New[1] = -(2.0f * pTag->axis[0][iG2_TRISIDE_MIDDLE]); v3New[2] = 2.0f * pTag->axis[2][iG2_TRISIDE_MIDDLE]; VectorSubtract/*Add*/(pTag->origin,v3New,v3); } else { v3New[0] = pTag->axis[1][iG2_TRISIDE_LONGEST]; v3New[1] = -pTag->axis[0][iG2_TRISIDE_LONGEST]; v3New[2] = pTag->axis[2][iG2_TRISIDE_LONGEST]; VectorSubtract(pTag->origin,v3New,v3); } // return (vec3_t*) &v3; Matrix4 Swap; Swap.Identity(); if (1) { Swap.SetRow(0,Vect3(0.0f,-1.0f,0.0f)); Swap.SetRow(1,Vect3(1.0f,0.0f,0.0f)); } Swap.CalcFlags(); Vect3 v3In((const float *)v3); static Vect3 v3Out; Swap.XFormVect(v3Out,v3In); return (vec3_t*) &v3Out; } } } default: { static bool bAlreadyReported = false; if (!bAlreadyReported) { bAlreadyReported = true; assert(0); ErrorBox(va("G2Exporter_Surface_GetVertCoords(): Error! modeltype %d has no case handler!\n",pModel->modelType)); } } break; } return &v3; } vec2_t *G2Exporter_Surface_GetTexCoords(int iSurfaceIndex, int iVertIndex, int iLOD) { gl_model* pModel = Model_AccountForLOD(gpModel, iLOD); static vec2_t v2={0}; switch (pModel->modelType) { case MODTYPE_MD3: { if (iSurfaceIndex < (int)gpModel->iNumMeshes) // gpModel, not pModel, to cope with models of differing # meshes { if (iSurfaceIndex < (int)pModel->iNumMeshes) // now check surface is legal for THIS LOD model... { // standard surface... // gl_mesh *pMesh = &pModel->pMeshes[iSurfaceIndex]; return &pMesh->textureCoord[iVertIndex]; } else { // surface was a legal index for LOD 0, but not for this one, so return fake surface info... // memset(v2,0,sizeof(v2)); return &v2; } } else { // tag surface... // // Texture coords aren't relevant for tag-surfaces, so... // memset(v2,0,sizeof(v2)); return &v2; } } default: { static bool bAlreadyReported = false; if (!bAlreadyReported) { bAlreadyReported = true; assert(0); ErrorBox(va("G2Exporter_Surface_GetTexCoords(): Error! modeltype %d has no case handler!\n",pModel->modelType)); } } break; } return &v2; } // returns NULL for ok, else error message of problem... // const char *loadmesh( gl_mesh& newMesh , FILE* F ) { LPCSTR psErrMess = NULL; char ID[5]; fread(ID, 4,1,F);ID[4]=0; if (strcmp(ID,"IDP3")==0) { char name[69]; UINT32 triangleStart; UINT32 headerSize; UINT32 texvecStart; UINT32 vertexStart; UINT32 meshSize; fread(name,68,1,F);name[65]=0; //65 chars, 32-bit aligned == 68 chars in file strcpy(newMesh.sName,name); newMesh.iNumMeshFrames = get32(F); newMesh.iNumSkins = get32(F); newMesh.iNumVertices = get32(F); newMesh.iNumTriangles = get32(F); triangleStart = get32(F); headerSize = get32(F); texvecStart = get32(F); vertexStart = get32(F); meshSize = get32(F); if ( ( headerSize == 108 ) && //header should be 108 bytes long ( meshSize > triangleStart ) && ( meshSize > texvecStart ) && ( meshSize > vertexStart ) ) { unsigned int i,j; char texName[68]; newMesh.meshFrames = new FMeshFrame[newMesh.iNumMeshFrames]; newMesh.triangles = new TriVec [newMesh.iNumTriangles ]; newMesh.textureCoord = new TexVec [newMesh.iNumVertices ]; newMesh.iterMesh = new Vec3 [newMesh.iNumVertices ]; newMesh.bindings = new GLuint [newMesh.iNumSkins ]; for (i=0;iversion); if (version != MD4_VERSION) { Error( "R_LoadMD4: %s has wrong version (%i should be %i)\n", mod_name, version, MD4_VERSION); return qfalse; } mod->modelType = MODTYPE_MDR; size = LittleLong(pinmodel->ofsEnd); mod->dataSize += size; md4 = mod->md4 = (md4Header_t*) malloc ( size ); memcpy( md4, buffer, LittleLong(pinmodel->ofsEnd) ); LL(md4->ident); LL(md4->version); LL(md4->numFrames); LL(md4->numBones); LL(md4->numLODs); LL(md4->ofsFrames); // this will be -ve for compressed models LL(md4->ofsLODs); LL(md4->numTags); LL(md4->ofsTags); LL(md4->ofsEnd); mod->numLods = md4->numLODs -1 ; //copy this up to the model for ease of use - it wil get inced after this. if ( md4->numFrames < 1 ) { Error ( "R_LoadMD4: %s has no frames\n", mod_name ); return qfalse; } if (md4->ofsFrames<0) { // compressed model... // // (actually, forget the swapping, this is only for MD3View which is only for Wintel anyway) } else { // uncompressed model... // // swap all the frames frameSize = (int)( &((md4Frame_t *)0)->bones[ md4->numBones ] ); for ( i = 0 ; i < md4->numFrames ; i++, frame++) { frame = (md4Frame_t *) ( (byte *)md4 + md4->ofsFrames + i * frameSize ); frame->radius = LittleFloat( frame->radius ); for ( j = 0 ; j < 3 ; j++ ) { frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] ); frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] ); frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] ); } for ( j = 0 ; j < (int) (md4->numBones * sizeof( md4Bone_t ) / 4) ; j++ ) { ((float *)frame->bones)[j] = LittleFloat( ((float *)frame->bones)[j] ); } } } // swap all the LOD's lod = (md4LOD_t *) ( (byte *)md4 + md4->ofsLODs ); for ( l = 0 ; l < md4->numLODs ; l++) { // swap all the surfaces surf = (md4Surface_t *) ( (byte *)lod + lod->ofsSurfaces ); for ( i = 0 ; i < lod->numSurfaces ; i++) { LL(surf->numTriangles); LL(surf->ofsTriangles); LL(surf->numVerts); LL(surf->ofsVerts); LL(surf->ofsEnd); LL(surf->ofsHeader); if ( surf->numVerts > SHADER_MAX_VERTEXES ) { Error ("R_LoadMD4: %s has more than %i verts on a surface (%i)", mod_name, SHADER_MAX_VERTEXES, surf->numVerts ); return qfalse; } if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) { Error ("R_LoadMD4: %s has more than %i triangles on a surface (%i)", mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles ); return qfalse; } // change to surface identifier surf->ident = SF_MD4; // lowercase the surface name so skin compares are faster strlwr( surf->name ); // strip off a trailing _1 or _2 // this is a crutch until i update carcass j = strlen( surf->name ); if ( j > 2 && surf->name[j-2] == '_' ) { surf->name[j-2] = 0; } /* // register the shaders sh = R_FindShader( surf->shader, LIGHTMAP_NONE, qtrue); if ( sh->defaultShader ) { surf->shaderIndex = 0; } else { surf->shaderIndex = sh->index; } */ surf->shaderIndex = loadTexture(surf->shader); // swap all the triangles tri = (md4Triangle_t *) ( (byte *)surf + surf->ofsTriangles ); for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) { LL(tri->indexes[0]); LL(tri->indexes[1]); LL(tri->indexes[2]); } // swap all the vertexes v = (md4Vertex_t *) ( (byte *)surf + surf->ofsVerts ); for ( j = 0 ; j < surf->numVerts ; j++ ) { v->normal[0] = LittleFloat( v->normal[0] ); v->normal[1] = LittleFloat( v->normal[1] ); v->normal[2] = LittleFloat( v->normal[2] ); v->texCoords[0] = LittleFloat( v->texCoords[0] ); v->texCoords[1] = LittleFloat( v->texCoords[1] ); v->numWeights = LittleLong( v->numWeights ); for ( k = 0 ; k < v->numWeights ; k++ ) { v->weights[k].boneIndex = LittleLong( v->weights[k].boneIndex ); v->weights[k].boneWeight = LittleFloat( v->weights[k].boneWeight ); v->weights[k].offset[0] = LittleFloat( v->weights[k].offset[0] ); v->weights[k].offset[1] = LittleFloat( v->weights[k].offset[1] ); v->weights[k].offset[2] = LittleFloat( v->weights[k].offset[2] ); } v = (md4Vertex_t *)&v->weights[v->numWeights]; } // find the next surface surf = (md4Surface_t *)( (byte *)surf + surf->ofsEnd ); } // find the next LOD lod = (md4LOD_t *)( (byte *)lod + lod->ofsEnd ); } tag = (md4Tag_t *) ( (byte *)md4 + md4->ofsTags ); for ( i = 0 ; i < md4->numTags ; i++) { LL(tag->boneIndex); tag++; } return qtrue; } void AxisClear( vec3_t axis[3] ) { axis[0][0] = 1; axis[0][1] = 0; axis[0][2] = 0; axis[1][0] = 0; axis[1][1] = 1; axis[1][2] = 0; axis[2][0] = 0; axis[2][1] = 0; axis[2][2] = 1; } void R_GetAnimTag( md4Header_t *mod, int framenum, int iTagIndex/*const char *tagName*/ ,md3Tag_t * dest) { int i; int frameSize; md4CompFrame_t *cframe; md4Frame_t *frame; md4Tag_t *tag; if ( framenum >= mod->numFrames ) { // it is possible to have a bad frame while changing models, so don't error framenum = mod->numFrames - 1; } tag = (md4Tag_t *)((byte *)mod + mod->ofsTags); for ( i = 0 ; i < mod->numTags ; i++, tag++ ) { if (iTagIndex == i) // if ( !strcmp( tag->name, tagName ) ) { strcpy(dest->name,tag->name); if (mod->ofsFrames <0) { // compressed model... // frameSize = (int)( &((md4CompFrame_t *)0)->bones[ mod->numBones ] ); cframe = (md4CompFrame_t *)((byte *)mod + (-mod->ofsFrames) + framenum * frameSize ); md4Bone_t Bone; MC_UnCompress(Bone.matrix, cframe->bones[tag->boneIndex].Comp); VectorCopy(&Bone.matrix[0][0], dest->axis[0] ); VectorCopy(&Bone.matrix[1][0], dest->axis[1] ); VectorCopy(&Bone.matrix[2][0], dest->axis[2] ); dest->origin[0]=Bone.matrix[0][3]; dest->origin[1]=Bone.matrix[1][3]; dest->origin[2]=Bone.matrix[2][3]; return; } else { // uncompressed model... // frameSize = (int)( &((md4Frame_t *)0)->bones[ mod->numBones ] ); frame = (md4Frame_t *)((byte *)mod + mod->ofsFrames + framenum * frameSize ); #if 1 VectorCopy(&frame->bones[tag->boneIndex].matrix[0][0], dest->axis[0] ); VectorCopy(&frame->bones[tag->boneIndex].matrix[1][0], dest->axis[1] ); VectorCopy(&frame->bones[tag->boneIndex].matrix[2][0], dest->axis[2] ); #else //transpose rot part { int j,k; for (j=0;j<3;j++) { for (k=0;k<3;k++) dest->axis[j][k]=frame->bones[tag->boneIndex].matrix[k][j]; } } #endif dest->origin[0]=frame->bones[tag->boneIndex].matrix[0][3]; dest->origin[1]=frame->bones[tag->boneIndex].matrix[1][3]; dest->origin[2]=frame->bones[tag->boneIndex].matrix[2][3]; return; } } } AxisClear( dest->axis ); VectorClear( dest->origin ); strcpy(dest->name,""); } bool loadmodel( gl_model& newModel , FILE* F, char *filename ) { // some generic stuff... // newModel.modelType = MODTYPE_BAD; newModel.modelListPosition = NULL; newModel.parent = NULL; newModel.currentFrame = 0; newModel.isBlended = false; newModel.blendParam1 = GL_ONE; newModel.blendParam2 = GL_ONE; // now work out the model type... char ID[5]; ID[0] = fgetc(F); ID[1] = fgetc(F); ID[2] = fgetc(F); ID[3] = fgetc(F); ID[4] = 0; if (strcmp(ID,"IDP3")==0) { newModel.modelType = MODTYPE_MD3; char name[69]; UINT32 maxskinNum; UINT32 headerSize; UINT32 tagStart; UINT32 tagEnd; UINT32 fileSize; UINT32 version; version = get32(F); fread(name,68,1,F);name[65]=0; //65 chars, 32-bit aligned == 68 chars in file newModel.iNumFrames = get32(F); newModel.iNumTags = get32(F); newModel.iNumMeshes = get32(F); maxskinNum = get32(F); headerSize = get32(F); tagStart = get32(F); tagEnd = get32(F); fileSize = get32(F); if ( ( headerSize == 108 ) && //header should be 108 bytes long ( fileSize > tagStart ) && ( fileSize >= tagEnd ) && ( version == 15 ) ) { unsigned int i,j; //initialize matrix to identity matrix for (i=0;i<16;i++) newModel.CTM_matrix[ i ] = 0; newModel.CTM_matrix[ 1 ] = 1; newModel.CTM_matrix[ 6 ] = 1; newModel.CTM_matrix[ 11 ] = 1; newModel.CTM_matrix[ 16 ] = 1; newModel.pMD3BoundFrames = new md3BoundFrame_t[newModel.iNumFrames]; for (i=0;inumFrames; newModel.iNumTags = newModel.Q3ModelStuff.md4->numTags; if (newModel.iNumTags != 0) { newModel.linkedModels = new gl_model*[newModel.iNumTags]; for (int i=0;i<(int)newModel.iNumTags;i++) newModel.linkedModels[ i ] = NULL; newModel.tags = new TagFrame[newModel.iNumFrames]; for (int i=0;i<(int)newModel.iNumFrames;i++) { newModel.tags[ i ] = new Tag[newModel.iNumTags]; md4Tag_t* tags = (md4Tag_t*) ( ((int) newModel.Q3ModelStuff.md4->ofsTags) + ((int) newModel.Q3ModelStuff.md4) ); for (int j=0;j<(int)newModel.iNumTags;j++) { R_GetAnimTag( newModel.Q3ModelStuff.md4, i, j , &newModel.tags[i][j]); } } } else { newModel.linkedModels = NULL; newModel.tags = NULL; } } free(pbLoadedFile); return bReturn; } else { ErrorBox(va("Unable to work out model format for file:\n\n\"%s\"\n",filename)); } } return false; } /* cleans up allocated model data */ void delete_gl_model( gl_model *model ) { unsigned int i, j, t; gl_mesh *mesh; gl_model *linkModel, *parent; if (model == NULL) return; // delete tag and bone frame data if (model->tags) { for (i=0; iiNumFrames;i++) { if (model->tags[i]) { delete [] model->tags[i]; model->tags[i] = NULL; } } } if (model->tags) { delete [] model->tags; model->tags = NULL; } if (model->pMD3BoundFrames) { delete [] model->pMD3BoundFrames; model->pMD3BoundFrames = NULL; } // remove all meshes for (i=0 ; iiNumMeshes ; i++) { mesh = &model->pMeshes[i]; if (mesh->triangles) { delete [] mesh->triangles; mesh->triangles = NULL; } if (mesh->textureCoord) { delete [] mesh->textureCoord; mesh->textureCoord = NULL; } if (mesh->iterMesh) { delete [] mesh->iterMesh; mesh->iterMesh = NULL; } // free textures from the mesh if (mesh->bindings) { for (t=0 ; tiNumSkins ; t++) freeTexture( mesh->bindings[t] ); delete [] mesh->bindings; mesh->bindings = NULL; } for (j=0 ; jiNumMeshFrames ; j++) { if (mesh->meshFrames[j].pVerts) { delete [] mesh->meshFrames[j].pVerts; mesh->meshFrames[j].pVerts = NULL; } if (mesh->meshFrames[j].pNormalIndexes) { delete [] mesh->meshFrames[j].pNormalIndexes; mesh->meshFrames[j].pNormalIndexes = NULL; } } if (mesh->meshFrames) { delete [] mesh->meshFrames; mesh->meshFrames = NULL; } } if (model->pMeshes) { delete [] model->pMeshes; model->pMeshes = NULL; } // remove references from parent parent = model->parent; if (parent != NULL) { for (i=0 ; iiNumTags ; i++) { if (parent->linkedModels[i] == model) { parent->linkedModels[i] = NULL; } } model->parent = NULL; } if (model->pModel_LOD1) { delete_gl_model( model->pModel_LOD1 ); model->pModel_LOD1 = NULL; } if (model->pModel_LOD2) { delete_gl_model( model->pModel_LOD2 ); model->pModel_LOD2 = NULL; } if (model->linkedModels) { // remove references from children for (i=0 ; iiNumTags ; i++) { linkModel = model->linkedModels[i]; if (linkModel != NULL) { //linkModel->parent = NULL; delete_gl_model( linkModel ); } } } if (!model->pModel_LOD0) // only do this if we're not an MD3 LOD model { // remove the model from mdview.modelList if (model->modelListPosition) { mdview.modelList->remove( model->modelListPosition ); if (model == mdview.baseModel) { mdview.baseModel = NULL; } } // remove all entries in the tag menu for (i=0 ; iiNumTags ; i++) { tagMenu_remove( (GLMODEL_DBLPTR)&model->linkedModels[i] ); } } // if it was an MDR file... // if (model->Q3ModelStuff.md4) { free(model->Q3ModelStuff.md4); model->Q3ModelStuff.md4 = NULL; } delete model; } bool loadmdx(gl_model& newModel, char* filename) { FILE* F; if ( ( F = fopen( filename , "rb" ) ) != NULL ) { bool bRet = loadmodel(newModel,F, filename); fclose(F); return bRet; } else return false; } /* writes out the mesh to a raw file */ void write_baseModelToRaw( char *fname ) { gl_model *model = mdview.baseModel; gl_mesh *mesh; int i, mesh_num; Vec3 *vecs; TriVec *tris; int tri_num, t, v, n; // open up file for writing FILE *rawfile = fopen( fname, "wt" ); if (!rawfile) { Debug("Could not write to raw file %s", fname); return; } // write out header fprintf(rawfile, "Object1\n"); mesh_num = model->iNumMeshes; // for all the meshes in a model for (i=0 ; ipMeshes[i]; vecs = mesh->meshFrames[model->currentFrame].pVerts; tris = mesh->triangles; tri_num = mesh->iNumTriangles; // write out all the triangles as 3 vertices for (t=0 ; t