//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //===========================================================================// // // write.c: writes a studio .mdl file // #pragma warning( disable : 4244 ) #pragma warning( disable : 4237 ) #pragma warning( disable : 4305 ) #include #include #include #include #include #include "cmdlib.h" #include "scriplib.h" #include "mathlib/mathlib.h" #include "studio.h" #include "studiomdl.h" #include "collisionmodel.h" #include "optimize.h" #include "studiobyteswap.h" #include "byteswap.h" #include "materialsystem/imaterial.h" #include "materialsystem/imaterialvar.h" #include "mdlobjects/dmeboneflexdriver.h" #include "perfstats.h" #include "tier1/smartptr.h" #include "tier2/p4helpers.h" int totalframes = 0; float totalseconds = 0; extern int numcommandnodes; // WriteFile is the only externally visible function in this file. // pData points to the current location in an output buffer and pStart is // the beginning of the buffer. bool FixupToSortedLODVertexes( studiohdr_t *pStudioHdr ); bool Clamp_RootLOD( studiohdr_t *phdr ); static void WriteAllSwappedFiles( const char *filename ); /* ============ WriteModel ============ */ static byte *pData; static byte *pStart; static byte *pBlockData; static byte *pBlockStart; #undef ALIGN4 #undef ALIGN16 #undef ALIGN32 #define ALIGN4( a ) a = (byte *)((int)((byte *)a + 3) & ~ 3) #define ALIGN16( a ) a = (byte *)((int)((byte *)a + 15) & ~ 15) #define ALIGN32( a ) a = (byte *)((int)((byte *)a + 31) & ~ 31) #define ALIGN64( a ) a = (byte *)((int)((byte *)a + 63) & ~ 63) #define ALIGN512( a ) a = (byte *)((int)((byte *)a + 511) & ~ 511) // make sure kalloc aligns to maximum alignment size #define FILEBUFFER (8 * 1024 * 1024) void WriteSeqKeyValues( mstudioseqdesc_t *pseqdesc, CUtlVector< char > *pKeyValue ); //----------------------------------------------------------------------------- // Purpose: stringtable is a session global string table. //----------------------------------------------------------------------------- struct stringtable_t { byte *base; int *ptr; const char *string; int dupindex; byte *addr; }; static int numStrings; static stringtable_t strings[32768]; static void BeginStringTable( ) { strings[0].base = NULL; strings[0].ptr = NULL; strings[0].string = ""; strings[0].dupindex = -1; numStrings = 1; } //----------------------------------------------------------------------------- // Purpose: add a string to the file-global string table. // Keep track of fixup locations //----------------------------------------------------------------------------- static void AddToStringTable( void *base, int *ptr, const char *string ) { for (int i = 0; i < numStrings; i++) { if (!string || !strcmp( string, strings[i].string )) { strings[numStrings].base = (byte *)base; strings[numStrings].ptr = ptr; strings[numStrings].string = string; strings[numStrings].dupindex = i; numStrings++; return; } } strings[numStrings].base = (byte *)base; strings[numStrings].ptr = ptr; strings[numStrings].string = string; strings[numStrings].dupindex = -1; numStrings++; } //----------------------------------------------------------------------------- // Purpose: Write out stringtable // fixup local pointers //----------------------------------------------------------------------------- static byte *WriteStringTable( byte *pData ) { // force null at first address strings[0].addr = pData; *pData = '\0'; pData++; // save all the rest for (int i = 1; i < numStrings; i++) { if (strings[i].dupindex == -1) { // not in table yet // calc offset relative to local base *strings[i].ptr = pData - strings[i].base; // keep track of address in case of duplication strings[i].addr = pData; // copy string data, add a terminating \0 strcpy( (char *)pData, strings[i].string ); pData += strlen( strings[i].string ); *pData = '\0'; pData++; } else { // already in table, calc offset of existing string relative to local base *strings[i].ptr = strings[strings[i].dupindex].addr - strings[i].base; } } ALIGN4( pData ); return pData; } // compare function for qsort below static int BoneNameCompare( const void *elem1, const void *elem2 ) { int index1 = *(byte *)elem1; int index2 = *(byte *)elem2; // compare bones by name return strcmpi( g_bonetable[index1].name, g_bonetable[index2].name ); } static void WriteBoneInfo( studiohdr_t *phdr ) { int i, j, k; mstudiobone_t *pbone; mstudiobonecontroller_t *pbonecontroller; mstudioattachment_t *pattachment; mstudiobbox_t *pbbox; // save bone info pbone = (mstudiobone_t *)pData; phdr->numbones = g_numbones; phdr->boneindex = pData - pStart; char* pSurfacePropName = GetDefaultSurfaceProp( ); AddToStringTable( phdr, &phdr->surfacepropindex, pSurfacePropName ); phdr->contents = GetDefaultContents(); for (i = 0; i < g_numbones; i++) { AddToStringTable( &pbone[i], &pbone[i].sznameindex, g_bonetable[i].name ); pbone[i].parent = g_bonetable[i].parent; pbone[i].flags = g_bonetable[i].flags; pbone[i].procindex = 0; pbone[i].physicsbone = g_bonetable[i].physicsBoneIndex; pbone[i].pos = g_bonetable[i].pos; pbone[i].rot = g_bonetable[i].rot; pbone[i].posscale = g_bonetable[i].posscale; pbone[i].rotscale = g_bonetable[i].rotscale; MatrixInvert( g_bonetable[i].boneToPose, pbone[i].poseToBone ); pbone[i].qAlignment = g_bonetable[i].qAlignment; AngleQuaternion( RadianEuler( g_bonetable[i].rot[0], g_bonetable[i].rot[1], g_bonetable[i].rot[2] ), pbone[i].quat ); QuaternionAlign( pbone[i].qAlignment, pbone[i].quat, pbone[i].quat ); pSurfacePropName = GetSurfaceProp( g_bonetable[i].name ); AddToStringTable( &pbone[i], &pbone[i].surfacepropidx, pSurfacePropName ); pbone[i].contents = GetContents( g_bonetable[i].name ); } pData += g_numbones * sizeof( mstudiobone_t ); ALIGN4( pData ); // save procedural bone info if (g_numaxisinterpbones) { mstudioaxisinterpbone_t *pProc = (mstudioaxisinterpbone_t *)pData; for (i = 0; i < g_numaxisinterpbones; i++) { j = g_axisinterpbonemap[i]; k = g_axisinterpbones[j].bone; pbone[k].procindex = (byte *)&pProc[i] - (byte *)&pbone[k]; pbone[k].proctype = STUDIO_PROC_AXISINTERP; // printf("bone %d %d\n", j, pbone[k].procindex ); pProc[i].control = g_axisinterpbones[j].control; pProc[i].axis = g_axisinterpbones[j].axis; for (k = 0; k < 6; k++) { VectorCopy( g_axisinterpbones[j].pos[k], pProc[i].pos[k] ); pProc[i].quat[k] = g_axisinterpbones[j].quat[k]; } } pData += g_numaxisinterpbones * sizeof( mstudioaxisinterpbone_t ); ALIGN4( pData ); } if (g_numquatinterpbones) { mstudioquatinterpbone_t *pProc = (mstudioquatinterpbone_t *)pData; pData += g_numquatinterpbones * sizeof( mstudioquatinterpbone_t ); ALIGN4( pData ); for (i = 0; i < g_numquatinterpbones; i++) { j = g_quatinterpbonemap[i]; k = g_quatinterpbones[j].bone; pbone[k].procindex = (byte *)&pProc[i] - (byte *)&pbone[k]; pbone[k].proctype = STUDIO_PROC_QUATINTERP; // printf("bone %d %d\n", j, pbone[k].procindex ); pProc[i].control = g_quatinterpbones[j].control; mstudioquatinterpinfo_t *pTrigger = (mstudioquatinterpinfo_t *)pData; pProc[i].numtriggers = g_quatinterpbones[j].numtriggers; pProc[i].triggerindex = (byte *)pTrigger - (byte *)&pProc[i]; pData += pProc[i].numtriggers * sizeof( mstudioquatinterpinfo_t ); for (k = 0; k < pProc[i].numtriggers; k++) { pTrigger[k].inv_tolerance = 1.0 / g_quatinterpbones[j].tolerance[k]; pTrigger[k].trigger = g_quatinterpbones[j].trigger[k]; pTrigger[k].pos = g_quatinterpbones[j].pos[k]; pTrigger[k].quat = g_quatinterpbones[j].quat[k]; } } } if (g_numjigglebones) { mstudiojigglebone_t *jiggleInfo = (mstudiojigglebone_t *)pData; for (i = 0; i < g_numjigglebones; i++) { j = g_jigglebonemap[i]; k = g_jigglebones[j].bone; pbone[k].procindex = (byte *)&jiggleInfo[i] - (byte *)&pbone[k]; pbone[k].proctype = STUDIO_PROC_JIGGLE; jiggleInfo[i] = g_jigglebones[j].data; } pData += g_numjigglebones * sizeof( mstudiojigglebone_t ); ALIGN4( pData ); } // write aim at bones if (g_numaimatbones) { mstudioaimatbone_t *pProc = (mstudioaimatbone_t *)pData; for (i = 0; i < g_numaimatbones; i++) { j = g_aimatbonemap[i]; k = g_aimatbones[j].bone; pbone[k].procindex = (byte *)&pProc[i] - (byte *)&pbone[k]; pbone[k].proctype = g_aimatbones[j].aimAttach == -1 ? STUDIO_PROC_AIMATBONE : STUDIO_PROC_AIMATATTACH; pProc[i].parent = g_aimatbones[j].parent; pProc[i].aim = g_aimatbones[j].aimAttach == -1 ? g_aimatbones[j].aimBone : g_aimatbones[j].aimAttach; pProc[i].aimvector = g_aimatbones[j].aimvector; pProc[i].upvector = g_aimatbones[j].upvector; pProc[i].basepos = g_aimatbones[j].basepos; } pData += g_numaimatbones * sizeof( mstudioaimatbone_t ); ALIGN4( pData ); } // map g_bonecontroller to bones for (i = 0; i < g_numbones; i++) { for (j = 0; j < 6; j++) { pbone[i].bonecontroller[j] = -1; } } for (i = 0; i < g_numbonecontrollers; i++) { j = g_bonecontroller[i].bone; switch( g_bonecontroller[i].type & STUDIO_TYPES ) { case STUDIO_X: pbone[j].bonecontroller[0] = i; break; case STUDIO_Y: pbone[j].bonecontroller[1] = i; break; case STUDIO_Z: pbone[j].bonecontroller[2] = i; break; case STUDIO_XR: pbone[j].bonecontroller[3] = i; break; case STUDIO_YR: pbone[j].bonecontroller[4] = i; break; case STUDIO_ZR: pbone[j].bonecontroller[5] = i; break; default: MdlError("unknown g_bonecontroller type\n"); } } // save g_bonecontroller info pbonecontroller = (mstudiobonecontroller_t *)pData; phdr->numbonecontrollers = g_numbonecontrollers; phdr->bonecontrollerindex = pData - pStart; for (i = 0; i < g_numbonecontrollers; i++) { pbonecontroller[i].bone = g_bonecontroller[i].bone; pbonecontroller[i].inputfield = g_bonecontroller[i].inputfield; pbonecontroller[i].type = g_bonecontroller[i].type; pbonecontroller[i].start = g_bonecontroller[i].start; pbonecontroller[i].end = g_bonecontroller[i].end; } pData += g_numbonecontrollers * sizeof( mstudiobonecontroller_t ); ALIGN4( pData ); // save attachment info pattachment = (mstudioattachment_t *)pData; phdr->numlocalattachments = g_numattachments; phdr->localattachmentindex = pData - pStart; for (i = 0; i < g_numattachments; i++) { pattachment[i].localbone = g_attachment[i].bone; AddToStringTable( &pattachment[i], &pattachment[i].sznameindex, g_attachment[i].name ); MatrixCopy( g_attachment[i].local, pattachment[i].local ); pattachment[i].flags = g_attachment[i].flags; } pData += g_numattachments * sizeof( mstudioattachment_t ); ALIGN4( pData ); // save hitbox sets phdr->numhitboxsets = g_hitboxsets.Size(); // Remember start spot mstudiohitboxset_t *hitboxset = (mstudiohitboxset_t *)pData; phdr->hitboxsetindex = pData - pStart; pData += phdr->numhitboxsets * sizeof( mstudiohitboxset_t ); ALIGN4( pData ); for ( int s = 0; s < g_hitboxsets.Size(); s++, hitboxset++ ) { s_hitboxset *set = &g_hitboxsets[ s ]; AddToStringTable( hitboxset, &hitboxset->sznameindex, set->hitboxsetname ); hitboxset->numhitboxes = set->numhitboxes; hitboxset->hitboxindex = ( pData - (byte *)hitboxset ); // save bbox info pbbox = (mstudiobbox_t *)pData; for (i = 0; i < hitboxset->numhitboxes; i++) { pbbox[i].bone = set->hitbox[i].bone; pbbox[i].group = set->hitbox[i].group; VectorCopy( set->hitbox[i].bmin, pbbox[i].bbmin ); VectorCopy( set->hitbox[i].bmax, pbbox[i].bbmax ); pbbox[i].szhitboxnameindex = 0; AddToStringTable( &(pbbox[i]), &(pbbox[i].szhitboxnameindex), set->hitbox[i].hitboxname ); } pData += hitboxset->numhitboxes * sizeof( mstudiobbox_t ); ALIGN4( pData ); } byte *pBoneTable = pData; phdr->bonetablebynameindex = (pData - pStart); // make a table in bone order and sort it with qsort for ( i = 0; i < phdr->numbones; i++ ) { pBoneTable[i] = i; } qsort( pBoneTable, phdr->numbones, sizeof(byte), BoneNameCompare ); pData += phdr->numbones * sizeof( byte ); ALIGN4( pData ); } // load a preexisting model to remember its sequence names and indices CUtlVector< CUtlString > g_vecPreexistingSequences; void LoadPreexistingSequenceOrder( const char *pFilename ) { g_vecPreexistingSequences.RemoveAll(); if ( !FileExists( pFilename ) ) return; Msg( "Loading preexisting model: %s\n", pFilename ); studiohdr_t *pStudioHdr; int len = LoadFile((char*)pFilename, (void **)&pStudioHdr); if ( len && pStudioHdr && pStudioHdr->SequencesAvailable() ) { Msg( " Found %i preexisting sequences.\n", pStudioHdr->GetNumSeq() ); for ( int i=0; iGetNumSeq(); i++ ) { //Msg( " Sequence %i : \"%s\"\n", i, pStudioHdr->pSeqdesc(i).pszLabel() ); g_vecPreexistingSequences.AddToTail( pStudioHdr->pSeqdesc(i).pszLabel() ); } } else { MdlWarning( "Zero-size file or no sequences.\n" ); } } static void WriteSequenceInfo( studiohdr_t *phdr ) { int i, j, k; mstudioseqdesc_t *pseqdesc; mstudioseqdesc_t *pbaseseqdesc; mstudioevent_t *pevent; byte *ptransition; // write models to disk with this flag set false. This will force // the sequences to be indexed by activity whenever the g_model is loaded // from disk. phdr->activitylistversion = 0; phdr->eventsindexed = 0; // save g_sequence info pseqdesc = (mstudioseqdesc_t *)pData; pbaseseqdesc = pseqdesc; phdr->numlocalseq = g_sequence.Count(); phdr->localseqindex = (pData - pStart); pData += g_sequence.Count() * sizeof( mstudioseqdesc_t ); bool bErrors = false; // build a table to remap new sequence indices to match the preexisting model bool bUseSeqOrderRemapping = false; int nSeqOrderRemappingTable[MAXSTUDIOSEQUENCES]; for (i=0; i vecNewIndices; vecNewIndices.RemoveAll(); // map current sequences to their old indices for (i = 0; i < g_sequence.Count(); i++ ) { int nIdx = g_vecPreexistingSequences.Find( g_sequence[i].name ); if ( nIdx >= 0 ) { nSeqOrderRemappingTable[nIdx] = i; } else { if ( i < g_vecPreexistingSequences.Count() ) { Msg( " Found new sequence \"%s\" using index of old sequence \"%s\".\n", g_sequence[i].name, g_vecPreexistingSequences[i].String() ); } else { Msg( " Found new sequence \"%s\".\n", g_sequence[i].name ); } vecNewIndices.AddToTail(i); } } // slot new sequences into unused indices while ( vecNewIndices.Count() ) { for (i = 0; i < MAXSTUDIOSEQUENCES; i++ ) { if ( nSeqOrderRemappingTable[i] == -1 ) { nSeqOrderRemappingTable[i] = vecNewIndices[0]; vecNewIndices.Remove(0); break; } } } // verify no indices are undefined for (i = 0; i < g_sequence.Count(); i++ ) { if ( nSeqOrderRemappingTable[i] == -1 ) { if ( bAllowSequenceRemoval ) { do { for ( int nB=i; nBszlabelindex, g_sequence[i].name ); AddToStringTable( pseqdesc, &pseqdesc->szactivitynameindex, g_sequence[i].activityname ); pseqdesc->baseptr = pStart - (byte *)pseqdesc; pseqdesc->flags = g_sequence[i].flags; pseqdesc->numblends = g_sequence[i].numblends; pseqdesc->groupsize[0] = g_sequence[i].groupsize[0]; pseqdesc->groupsize[1] = g_sequence[i].groupsize[1]; pseqdesc->paramindex[0] = g_sequence[i].paramindex[0]; pseqdesc->paramstart[0] = g_sequence[i].paramstart[0]; pseqdesc->paramend[0] = g_sequence[i].paramend[0]; pseqdesc->paramindex[1] = g_sequence[i].paramindex[1]; pseqdesc->paramstart[1] = g_sequence[i].paramstart[1]; pseqdesc->paramend[1] = g_sequence[i].paramend[1]; if (g_sequence[i].groupsize[0] > 1 || g_sequence[i].groupsize[1] > 1) { // save posekey values float *pposekey = (float *)pData; pseqdesc->posekeyindex = (pData - pSequenceStart); pData += (pseqdesc->groupsize[0] + pseqdesc->groupsize[1]) * sizeof( float ); for (j = 0; j < pseqdesc->groupsize[0]; j++) { *(pposekey++) = g_sequence[i].param0[j]; // printf("%.2f ", g_sequence[i].param0[j] ); } for (j = 0; j < pseqdesc->groupsize[1]; j++) { *(pposekey++) = g_sequence[i].param1[j]; // printf("%.2f ", g_sequence[i].param1[j] ); } // printf("\n" ); } // pseqdesc->motiontype = g_sequence[i].motiontype; // pseqdesc->motionbone = 0; // g_sequence[i].motionbone; // VectorCopy( g_sequence[i].linearmovement, pseqdesc->linearmovement ); pseqdesc->activity = g_sequence[i].activity; pseqdesc->actweight = g_sequence[i].actweight; pseqdesc->bbmin = g_sequence[i].bmin; pseqdesc->bbmax = g_sequence[i].bmax; pseqdesc->fadeintime = g_sequence[i].fadeintime; pseqdesc->fadeouttime = g_sequence[i].fadeouttime; pseqdesc->localentrynode = g_sequence[i].entrynode; pseqdesc->localexitnode = g_sequence[i].exitnode; //pseqdesc->entryphase = g_sequence[i].entryphase; //pseqdesc->exitphase = g_sequence[i].exitphase; pseqdesc->nodeflags = g_sequence[i].nodeflags; // save events pevent = (mstudioevent_t *)pData; pseqdesc->numevents = g_sequence[i].numevents; pseqdesc->eventindex = (pData - pSequenceStart); pData += pseqdesc->numevents * sizeof( mstudioevent_t ); for (j = 0; j < g_sequence[i].numevents; j++) { k = g_sequence[i].panim[0][0]->numframes - 1; if (g_sequence[i].event[j].frame <= k) pevent[j].cycle = g_sequence[i].event[j].frame / ((float)k); else if (k == 0 && g_sequence[i].event[j].frame == 0) pevent[j].cycle = 0; else { MdlWarning("Event %d (frame %d) out of range in %s\n", g_sequence[i].event[j].event, g_sequence[i].event[j].frame, g_sequence[i].name ); bErrors = true; } //Adrian - Remove me once we phase out the old event system. if ( V_isdigit( g_sequence[i].event[j].eventname[0] ) ) { pevent[j].event = atoi( g_sequence[i].event[j].eventname ); pevent[j].type = 0; pevent[j].szeventindex = 0; } else { AddToStringTable( &pevent[j], &pevent[j].szeventindex, g_sequence[i].event[j].eventname ); pevent[j].type = NEW_EVENT_STYLE; } // printf("%4d : %d %f\n", pevent[j].event, g_sequence[i].event[j].frame, pevent[j].cycle ); // AddToStringTable( &pevent[j], &pevent[j].szoptionindex, g_sequence[i].event[j].options ); strcpy( pevent[j].options, g_sequence[i].event[j].options ); } ALIGN4( pData ); // save ikrules pseqdesc->numikrules = g_sequence[i].numikrules; // save autolayers mstudioautolayer_t *pautolayer = (mstudioautolayer_t *)pData; pseqdesc->numautolayers = g_sequence[i].numautolayers; pseqdesc->autolayerindex = (pData - pSequenceStart); pData += pseqdesc->numautolayers * sizeof( mstudioautolayer_t ); for (j = 0; j < g_sequence[i].numautolayers; j++) { pautolayer[j].iSequence = g_sequence[i].autolayer[j].sequence; pautolayer[j].iPose = g_sequence[i].autolayer[j].pose; pautolayer[j].flags = g_sequence[i].autolayer[j].flags; // autolayer indices are stored by index, so remap them now using the invertex lookup table if ( bUseSeqOrderRemapping ) { int nRemapAutoLayer = nSeqOrderRemappingTableInv[ pautolayer[j].iSequence ]; if ( nRemapAutoLayer != pautolayer[j].iSequence ) { Msg( " Autolayer remapping index %i to %i.\n", pautolayer[j].iSequence, nRemapAutoLayer ); pautolayer[j].iSequence = nRemapAutoLayer; } } if (!(pautolayer[j].flags & STUDIO_AL_POSE)) { pautolayer[j].start = g_sequence[i].autolayer[j].start / (g_sequence[i].panim[0][0]->numframes - 1); pautolayer[j].peak = g_sequence[i].autolayer[j].peak / (g_sequence[i].panim[0][0]->numframes - 1); pautolayer[j].tail = g_sequence[i].autolayer[j].tail / (g_sequence[i].panim[0][0]->numframes - 1); pautolayer[j].end = g_sequence[i].autolayer[j].end / (g_sequence[i].panim[0][0]->numframes - 1); } else { pautolayer[j].start = g_sequence[i].autolayer[j].start; pautolayer[j].peak = g_sequence[i].autolayer[j].peak; pautolayer[j].tail = g_sequence[i].autolayer[j].tail; pautolayer[j].end = g_sequence[i].autolayer[j].end; } } // save boneweights float *pweight = 0; j = 0; // look up previous sequence weights and try to find a match for (k = 0; k < m; k++) { j = 0; // only check newer boneweights than the last one if (pseqdesc[k-m].pBoneweight( 0 ) > pweight) { pweight = pseqdesc[k-m].pBoneweight( 0 ); for (j = 0; j < g_numbones; j++) { // we're not walking the linear sequence list if we're remapping, so we need to remap this check int nRemap = k; if ( bUseSeqOrderRemapping ) nRemap = nSeqOrderRemappingTable[k]; if (g_sequence[i].weight[j] != g_sequence[nRemap].weight[j]) break; } if (j == g_numbones) break; } } // check to see if all the bones matched if (j < g_numbones) { // allocate new block //printf("new %08x\n", pData ); pweight = (float *)pData; pseqdesc->weightlistindex = (pData - pSequenceStart); pData += g_numbones * sizeof( float ); for (j = 0; j < g_numbones; j++) { pweight[j] = g_sequence[i].weight[j]; } } else { // use previous boneweight //printf("prev %08x\n", pweight ); pseqdesc->weightlistindex = ((byte *)pweight - pSequenceStart); } // save iklocks mstudioiklock_t *piklock = (mstudioiklock_t *)pData; pseqdesc->numiklocks = g_sequence[i].numiklocks; pseqdesc->iklockindex = (pData - pSequenceStart); pData += pseqdesc->numiklocks * sizeof( mstudioiklock_t ); ALIGN4( pData ); for (j = 0; j < pseqdesc->numiklocks; j++) { piklock->chain = g_sequence[i].iklock[j].chain; piklock->flPosWeight = g_sequence[i].iklock[j].flPosWeight; piklock->flLocalQWeight = g_sequence[i].iklock[j].flLocalQWeight; piklock++; } // Write animation blend parameters short *blends = ( short * )pData; pseqdesc->animindexindex = ( pData - pSequenceStart ); pData += ( g_sequence[i].groupsize[0] * g_sequence[i].groupsize[1] ) * sizeof( short ); ALIGN4( pData ); for ( j = 0; j < g_sequence[i].groupsize[0] ; j++ ) { for ( k = 0; k < g_sequence[i].groupsize[1]; k++ ) { // height value * width of row + width value int offset = k * g_sequence[i].groupsize[0] + j; if ( g_sequence[i].panim[j][k] ) { int animindex = g_sequence[i].panim[j][k]->index; Assert( animindex >= 0 && animindex < SHRT_MAX ); blends[ offset ] = (short)animindex; } else { blends[ offset ] = 0; } } } // Write cycle overrides pseqdesc->cycleposeindex = g_sequence[i].cycleposeindex; WriteSeqKeyValues( pseqdesc, &g_sequence[i].KeyValue ); } if (bErrors) { MdlError( "Exiting due to Errors\n"); } // save transition graph int *pxnodename = (int *)pData; phdr->localnodenameindex = (pData - pStart); pData += g_numxnodes * sizeof( *pxnodename ); ALIGN4( pData ); for (i = 0; i < g_numxnodes; i++) { AddToStringTable( phdr, pxnodename, g_xnodename[i+1] ); // printf("%d : %s\n", i, g_xnodename[i+1] ); pxnodename++; } ptransition = (byte *)pData; phdr->numlocalnodes = g_numxnodes; phdr->localnodeindex = pData - pStart; pData += g_numxnodes * g_numxnodes * sizeof( byte ); ALIGN4( pData ); for (i = 0; i < g_numxnodes; i++) { // printf("%2d (%12s) : ", i + 1, g_xnodename[i+1] ); for (j = 0; j < g_numxnodes; j++) { *ptransition++ = g_xnode[i][j]; // printf(" %2d", g_xnode[i][j] ); } // printf("\n" ); } } //----------------------------------------------------------------------------- // Purpose: Stub implementation // Input : *group - //----------------------------------------------------------------------------- const studiohdr_t *studiohdr_t::FindModel( void **cache, char const *modelname ) const { return NULL; } virtualmodel_t *studiohdr_t::GetVirtualModel( void ) const { return NULL; } const studiohdr_t *virtualgroup_t::GetStudioHdr( void ) const { return (studiohdr_t *)cache; } byte *studiohdr_t::GetAnimBlock( int i ) const { return NULL; } int studiohdr_t::GetAutoplayList( unsigned short **pOut ) const { return 0; } int rawanimbytes = 0; int animboneframes = 0; int numAxis[4] = { 0, 0, 0, 0 }; int numPos[4] = { 0, 0, 0, 0 }; int useRaw = 0; void WriteAnimationData( s_animation_t *srcanim, mstudioanimdesc_t *destanimdesc, byte *&pLocalData, byte *&pExtData ) { int j, k, n; byte *pData = NULL; for (int w = 0; w < srcanim->numsections; w++) { bool bUseExtData = false; pData = pLocalData; if (pExtData != NULL && !srcanim->disableAnimblocks && !(w == 0 && srcanim->isFirstSectionLocal)) { pData = pExtData; bUseExtData = true; } mstudioanim_t *destanim = (mstudioanim_t *)pData; byte *pStartSection = pData; pData += sizeof( *destanim ); destanim->bone = 255; mstudioanim_t *prevanim = NULL; // save animation value info for (j = 0; j < g_numbones; j++) { // destanim->weight = srcanim->weight[j]; // printf( "%s %.1f\n", g_bonetable[j].name, destanim->weight ); destanim->flags = 0; s_compressed_t *psrcdata = &srcanim->anim[w][j]; numPos[ (psrcdata->num[0] != 0) + (psrcdata->num[1] != 0) + (psrcdata->num[2] != 0) ]++; numAxis[ (psrcdata->num[3] != 0) + (psrcdata->num[4] != 0) + (psrcdata->num[5] != 0) ]++; if (psrcdata->num[0] + psrcdata->num[1] + psrcdata->num[2] + psrcdata->num[3] + psrcdata->num[4] + psrcdata->num[5] == 0) { // no animation, skip continue; } destanim->bone = j; // copy flags over if delta animation if (srcanim->flags & STUDIO_DELTA) { destanim->flags |= STUDIO_ANIM_DELTA; } if ((srcanim->numframes == 1) || (psrcdata->num[0] <= 2 && psrcdata->num[1] <= 2 && psrcdata->num[2] <= 2 && psrcdata->num[3] <= 2 && psrcdata->num[4] <= 2 && psrcdata->num[5] <= 2)) { // printf("%d : %d %d %d : %d %d %d\n", j, psrcdata->num[0], psrcdata->num[1], psrcdata->num[2], psrcdata->num[3], psrcdata->num[4], psrcdata->num[5] ); // single frame, if animation detected just store as raw int iFrame = min( w * srcanim->sectionframes, srcanim->numframes - 1 ); if (psrcdata->num[3] != 0 || psrcdata->num[4] != 0 || psrcdata->num[5] != 0) { Quaternion q; AngleQuaternion( srcanim->sanim[iFrame][j].rot, q ); *((Quaternion64 *)pData) = q; pData += sizeof( Quaternion64 ); rawanimbytes += sizeof( Quaternion64 ); destanim->flags |= STUDIO_ANIM_RAWROT2; } if (psrcdata->num[0] != 0 || psrcdata->num[1] != 0 || psrcdata->num[2] != 0) { *((Vector48 *)pData) = srcanim->sanim[iFrame][j].pos; pData += sizeof( Vector48 ); rawanimbytes += sizeof( Vector48 ); destanim->flags |= STUDIO_ANIM_RAWPOS; } } else { // look to see if storing raw quat's would have taken less space if (psrcdata->num[3] >= srcanim->numframes && psrcdata->num[4] >= srcanim->numframes && psrcdata->num[5] >= srcanim->numframes) { useRaw++; } mstudioanim_valueptr_t *posvptr = NULL; mstudioanim_valueptr_t *rotvptr = NULL; // allocate room for rotation ptrs rotvptr = (mstudioanim_valueptr_t *)pData; pData += sizeof( *rotvptr ); // skip all position info if there's no animation if (psrcdata->num[0] != 0 || psrcdata->num[1] != 0 || psrcdata->num[2] != 0) { posvptr = (mstudioanim_valueptr_t *)pData; pData += sizeof( *posvptr ); } mstudioanimvalue_t *destanimvalue = (mstudioanimvalue_t *)pData; if (rotvptr) { // store rotation animations for (k = 3; k < 6; k++) { if (psrcdata->num[k] == 0) { rotvptr->offset[k-3] = 0; } else { rotvptr->offset[k-3] = ((byte *)destanimvalue - (byte *)rotvptr); for (n = 0; n < psrcdata->num[k]; n++) { destanimvalue->value = psrcdata->data[k][n].value; destanimvalue++; } } } destanim->flags |= STUDIO_ANIM_ANIMROT; } if (posvptr) { // store position animations for (k = 0; k < 3; k++) { if (psrcdata->num[k] == 0) { posvptr->offset[k] = 0; } else { posvptr->offset[k] = ((byte *)destanimvalue - (byte *)posvptr); for (n = 0; n < psrcdata->num[k]; n++) { destanimvalue->value = psrcdata->data[k][n].value; destanimvalue++; } } } destanim->flags |= STUDIO_ANIM_ANIMPOS; } rawanimbytes += ((byte *)destanimvalue - pData); pData = (byte *)destanimvalue; } prevanim = destanim; destanim->nextoffset = pData - (byte *)destanim; destanim = (mstudioanim_t *)pData; pData += sizeof( *destanim ); } if (prevanim) { prevanim->nextoffset = 0; } ALIGN4( pData ); // write into anim blocks if needed if (destanimdesc->sectionindex) { if (bUseExtData) { if (g_numanimblocks && pData - g_animblock[g_numanimblocks-1].start > g_animblocksize) { // advance to next animblock g_animblock[g_numanimblocks-1].end = pStartSection; g_animblock[g_numanimblocks].start = pStartSection; g_numanimblocks++; } destanimdesc->pSection(w)->animblock = g_numanimblocks - 1; destanimdesc->pSection(w)->animindex = pStartSection - g_animblock[g_numanimblocks-1].start; } else { destanimdesc->pSection(w)->animblock = 0; destanimdesc->pSection(w)->animindex = pStartSection - (byte *)destanimdesc; } // printf("%s (%d) : %d:%d\n", srcanim->name, w, destanimdesc->pSection(w)->animblock, destanimdesc->pSection(w)->animindex ); } if (!bUseExtData) { pLocalData = pData; } else { pExtData = pData; } } } byte *WriteIkErrors( s_animation_t *srcanim, byte *pData ) { int j, k; // write IK error keys mstudioikrule_t *pikruledata = (mstudioikrule_t *)pData; pData += srcanim->numikrules * sizeof( *pikruledata ); ALIGN4( pData ); for (j = 0; j < srcanim->numikrules; j++) { mstudioikrule_t *pikrule = pikruledata + j; pikrule->index = srcanim->ikrule[j].index; pikrule->chain = srcanim->ikrule[j].chain; pikrule->bone = srcanim->ikrule[j].bone; pikrule->type = srcanim->ikrule[j].type; pikrule->slot = srcanim->ikrule[j].slot; pikrule->pos = srcanim->ikrule[j].pos; pikrule->q = srcanim->ikrule[j].q; pikrule->height = srcanim->ikrule[j].height; pikrule->floor = srcanim->ikrule[j].floor; pikrule->radius = srcanim->ikrule[j].radius; if (srcanim->numframes > 1.0) { pikrule->start = srcanim->ikrule[j].start / (srcanim->numframes - 1.0f); pikrule->peak = srcanim->ikrule[j].peak / (srcanim->numframes - 1.0f); pikrule->tail = srcanim->ikrule[j].tail / (srcanim->numframes - 1.0f); pikrule->end = srcanim->ikrule[j].end / (srcanim->numframes - 1.0f); pikrule->contact= srcanim->ikrule[j].contact / (srcanim->numframes - 1.0f); } else { pikrule->start = 0.0f; pikrule->peak = 0.0f; pikrule->tail = 1.0f; pikrule->end = 1.0f; pikrule->contact= 0.0f; } /* printf("%d %d %d %d : %.2f %.2f %.2f %.2f\n", srcanim->ikrule[j].start, srcanim->ikrule[j].peak, srcanim->ikrule[j].tail, srcanim->ikrule[j].end, pikrule->start, pikrule->peak, pikrule->tail, pikrule->end ); */ pikrule->iStart = srcanim->ikrule[j].start; #if 0 // uncompressed pikrule->ikerrorindex = (pData - (byte*)pikrule); mstudioikerror_t *perror = (mstudioikerror_t *)pData; pData += srcanim->ikrule[j].numerror * sizeof( *perror ); for (k = 0; k < srcanim->ikrule[j].numerror; k++) { perror[k].pos = srcanim->ikrule[j].pError[k].pos; perror[k].q = srcanim->ikrule[j].pError[k].q; } #endif #if 1 // skip writting the header if there's no IK data for (k = 0; k < 6; k++) { if (srcanim->ikrule[j].errorData.numanim[k]) break; } if (k == 6) continue; // compressed pikrule->compressedikerrorindex = (pData - (byte*)pikrule); mstudiocompressedikerror_t *pCompressed = (mstudiocompressedikerror_t *)pData; pData += sizeof( *pCompressed ); for (k = 0; k < 6; k++) { pCompressed->scale[k] = srcanim->ikrule[j].errorData.scale[k]; pCompressed->offset[k] = (pData - (byte*)pCompressed); int size = srcanim->ikrule[j].errorData.numanim[k] * sizeof( mstudioanimvalue_t ); memmove( pData, srcanim->ikrule[j].errorData.anim[k], size ); pData += size; } if (strlen( srcanim->ikrule[j].attachment ) > 0) { // don't use string table, we're probably not in the same file. int size = strlen( srcanim->ikrule[j].attachment ) + 1; strcpy( (char *)pData, srcanim->ikrule[j].attachment ); pikrule->szattachmentindex = pData - (byte *)pikrule; pData += size; } ALIGN4( pData ); #endif // AddToStringTable( pikrule, &pikrule->szattachmentindex, srcanim->ikrule[j].attachment ); } return pData; } byte *WriteLocalHierarchy( s_animation_t *srcanim, byte *pData ) { int j, k; // write hierarchy keys mstudiolocalhierarchy_t *pHierarchyData = (mstudiolocalhierarchy_t *)pData; pData += srcanim->numlocalhierarchy * sizeof( *pHierarchyData ); ALIGN4( pData ); for (j = 0; j < srcanim->numlocalhierarchy; j++) { mstudiolocalhierarchy_t *pHierarchy = pHierarchyData + j; pHierarchy->iBone = srcanim->localhierarchy[j].bone; pHierarchy->iNewParent = srcanim->localhierarchy[j].newparent; if (srcanim->numframes > 1.0) { pHierarchy->start = srcanim->localhierarchy[j].start / (srcanim->numframes - 1.0f); pHierarchy->peak = srcanim->localhierarchy[j].peak / (srcanim->numframes - 1.0f); pHierarchy->tail = srcanim->localhierarchy[j].tail / (srcanim->numframes - 1.0f); pHierarchy->end = srcanim->localhierarchy[j].end / (srcanim->numframes - 1.0f); } else { pHierarchy->start = 0.0f; pHierarchy->peak = 0.0f; pHierarchy->tail = 1.0f; pHierarchy->end = 1.0f; } pHierarchy->iStart = srcanim->localhierarchy[j].start; #if 0 // uncompressed pHierarchy->ikerrorindex = (pData - (byte*)pHierarchy); mstudioikerror_t *perror = (mstudioikerror_t *)pData; pData += srcanim->ikrule[j].numerror * sizeof( *perror ); for (k = 0; k < srcanim->ikrule[j].numerror; k++) { perror[k].pos = srcanim->ikrule[j].pError[k].pos; perror[k].q = srcanim->ikrule[j].pError[k].q; } #endif #if 1 // skip writting the header if there's no IK data for (k = 0; k < 6; k++) { if (srcanim->localhierarchy[j].localData.numanim[k]) break; } if (k == 6) continue; // compressed pHierarchy->localanimindex = (pData - (byte*)pHierarchy); mstudiocompressedikerror_t *pCompressed = (mstudiocompressedikerror_t *)pData; pData += sizeof( *pCompressed ); for (k = 0; k < 6; k++) { pCompressed->scale[k] = srcanim->localhierarchy[j].localData.scale[k]; pCompressed->offset[k] = (pData - (byte*)pCompressed); int size = srcanim->localhierarchy[j].localData.numanim[k] * sizeof( mstudioanimvalue_t ); memmove( pData, srcanim->localhierarchy[j].localData.anim[k], size ); pData += size; } ALIGN4( pData ); #endif // AddToStringTable( pHierarchy, &pHierarchy->szattachmentindex, srcanim->ikrule[j].attachment ); } return pData; } static byte *WriteAnimations( byte *pData, byte *pStart, studiohdr_t *phdr ) { int i, j; mstudioanimdesc_t *panimdesc; // save animations panimdesc = (mstudioanimdesc_t *)pData; if( phdr ) { phdr->numlocalanim = g_numani; phdr->localanimindex = (pData - pStart); } pData += g_numani * sizeof( *panimdesc ); ALIGN4( pData ); // ------------ ------- ------- : ------- (-------) if( g_verbose ) { printf(" animation x y ips angle\n"); } for (i = 0; i < g_numani; i++) { s_animation_t *srcanim = g_panimation[ i ]; mstudioanimdesc_t *destanim = &panimdesc[i]; Assert( srcanim ); AddToStringTable( destanim, &destanim->sznameindex, srcanim->name ); destanim->baseptr = pStart - (byte *)destanim; destanim->fps = srcanim->fps; destanim->flags = srcanim->flags; destanim->sectionframes = srcanim->sectionframes; totalframes += srcanim->numframes; totalseconds += srcanim->numframes / srcanim->fps; destanim->numframes = srcanim->numframes; // destanim->motiontype = srcanim->motiontype; // destanim->motionbone = srcanim->motionbone; // VectorCopy( srcanim->linearpos, destanim->linearpos ); j = srcanim->numpiecewisekeys - 1; if (srcanim->piecewisemove[j].pos[0] != 0 || srcanim->piecewisemove[j].pos[1] != 0) { float t = (srcanim->numframes - 1) / srcanim->fps; float r = 1 / t; float a = atan2( srcanim->piecewisemove[j].pos[1], srcanim->piecewisemove[j].pos[0] ) * (180 / M_PI); float d = sqrt( DotProduct( srcanim->piecewisemove[j].pos, srcanim->piecewisemove[j].pos ) ); if( g_verbose ) { printf("%12s %7.2f %7.2f : %7.2f (%7.2f) %.1f\n", srcanim->name, srcanim->piecewisemove[j].pos[0], srcanim->piecewisemove[j].pos[1], d * r, a, t ); } } if (srcanim->numsections > 1) { destanim->sectionindex = pData - (byte *)destanim; pData += srcanim->numsections * sizeof( mstudioanimsections_t ); } // VectorCopy( srcanim->linearrot, destanim->linearrot ); // destanim->automoveposindex = srcanim->automoveposindex; // destanim->automoveangleindex = srcanim->automoveangleindex; // align all animation data to cache line boundaries ALIGN16( pData ); ALIGN16( pBlockData ); if (pBlockStart) { // allocate the first block if needed if (g_numanimblocks == 0) { g_numanimblocks = 1; g_animblock[g_numanimblocks].start = pBlockData; g_numanimblocks++; } } if (!pBlockStart || (g_bonesaveframe.Count() == 0 && srcanim->numframes == 1)) { // hack srcanim->disableAnimblocks = true; } else if (g_bNoAnimblockStall) { srcanim->isFirstSectionLocal = true; } // block zero is relative to me g_animblock[0].start = (byte *)(destanim); byte *pAnimData = NULL; byte *pIkData = NULL; byte *pLocalHierarchy = NULL; byte *pBlockEnd = pBlockData; if (srcanim->disableAnimblocks || srcanim->isFirstSectionLocal) { destanim->animblock = 0; pAnimData = pData; WriteAnimationData( srcanim, destanim, pData, pBlockEnd ); pIkData = pData; pLocalHierarchy = WriteIkErrors( srcanim, pIkData ); pData = WriteLocalHierarchy( srcanim, pLocalHierarchy ); } else { pAnimData = pBlockEnd; WriteAnimationData( srcanim, destanim, pData, pBlockEnd ); if ( destanim->sectionindex ) { // if sections were written, don't move the data already written to the last block pBlockData = pBlockEnd; } destanim->animblock = g_numanimblocks-1; pIkData = pBlockEnd; pLocalHierarchy = WriteIkErrors( srcanim, pIkData ); pBlockEnd = WriteLocalHierarchy( srcanim, pLocalHierarchy ); } // printf("%d %x %x %x %s : %d\n", g_numanimblocks - 1, g_animblock[g_numanimblocks-1].start, pBlockData, pBlockEnd, srcanim->name, srcanim->numsections ); if (pBlockData != pBlockEnd && pBlockEnd - g_animblock[g_numanimblocks-1].start > g_animblocksize) { g_animblock[g_numanimblocks-1].end = pBlockData; g_animblock[g_numanimblocks].start = pBlockData; g_numanimblocks++; destanim->animblock = g_numanimblocks-1; } destanim->animindex = pAnimData - g_animblock[destanim->animblock].start; if ( srcanim->numikrules ) { destanim->numikrules = srcanim->numikrules; if (destanim->animblock == 0) { destanim->ikruleindex = pIkData - g_animblock[destanim->animblock].start; } else { destanim->animblockikruleindex = pIkData - g_animblock[destanim->animblock].start; } } if ( srcanim->numlocalhierarchy ) { destanim->numlocalhierarchy = srcanim->numlocalhierarchy; destanim->localhierarchyindex = pLocalHierarchy - g_animblock[destanim->animblock].start; } if (g_numanimblocks) { g_animblock[g_numanimblocks-1].end = pBlockEnd; pBlockData = pBlockEnd; } // printf("%s : %d:%d\n", srcanim->name, destanim->animblock, destanim->animindex ); // printf("raw bone data %d : %s\n", (byte *)destanimvalue - pData, srcanim->name); } if( !g_quiet ) { /* for (i = 0; i < g_numanimblocks; i++) { printf("%2d (%3d:%3d): %d\n", i, g_animblock[i].iStartAnim, g_animblock[i].iEndAnim, g_animblock[i].end - g_animblock[i].start ); } */ } if( !g_quiet ) { /* printf("raw anim data %d : %d\n", rawanimbytes, animboneframes ); printf("pos %d %d %d %d\n", numPos[0], numPos[1], numPos[2], numPos[3] ); printf("axis %d %d %d %d : %d\n", numAxis[0], numAxis[1], numAxis[2], numAxis[3], useRaw ); */ } // write movement keys for (i = 0; i < g_numani; i++) { s_animation_t *anim = g_panimation[ i ]; // panimdesc[i].entrancevelocity = anim->entrancevelocity; panimdesc[i].nummovements = anim->numpiecewisekeys; if (panimdesc[i].nummovements) { panimdesc[i].movementindex = pData - (byte*)&panimdesc[i]; mstudiomovement_t *pmove = (mstudiomovement_t *)pData; pData += panimdesc[i].nummovements * sizeof( *pmove ); ALIGN4( pData ); for (j = 0; j < panimdesc[i].nummovements; j++) { pmove[j].endframe = anim->piecewisemove[j].endframe; pmove[j].motionflags = anim->piecewisemove[j].flags; pmove[j].v0 = anim->piecewisemove[j].v0; pmove[j].v1 = anim->piecewisemove[j].v1; pmove[j].angle = RAD2DEG( anim->piecewisemove[j].rot[2] ); VectorCopy( anim->piecewisemove[j].vector, pmove[j].vector ); VectorCopy( anim->piecewisemove[j].pos, pmove[j].position ); } } } // only write zero frames if the animation data is demand loaded if (!pBlockStart) return pData; // calculate what bones should be have zero frame saved out if (g_bonesaveframe.Count() == 0) { for (j = 0; j < g_numbones; j++) { if ((g_bonetable[j].parent == -1) || (g_bonetable[j].posrange.Length() > 2.0)) { g_bonetable[j].flags |= BONE_HAS_SAVEFRAME_POS; } g_bonetable[j].flags |= BONE_HAS_SAVEFRAME_ROT; if ((!g_quiet) && (g_bonetable[j].flags & (BONE_HAS_SAVEFRAME_POS | BONE_HAS_SAVEFRAME_ROT))) { printf("$BoneSaveFrame \"%s\"", g_bonetable[j].name ); if (g_bonetable[j].flags & BONE_HAS_SAVEFRAME_POS) printf(" position" ); if (g_bonetable[j].flags & BONE_HAS_SAVEFRAME_ROT) printf(" rotation" ); printf("\n"); } } } else { for (i = 0; i < g_bonesaveframe.Count(); i++) { j = findGlobalBone( g_bonesaveframe[i].name ); if (j != -1) { if (g_bonesaveframe[i].bSavePos) { g_bonetable[j].flags |= BONE_HAS_SAVEFRAME_POS; } if (g_bonesaveframe[i].bSaveRot) { g_bonetable[j].flags |= BONE_HAS_SAVEFRAME_ROT; } } } } for (j = 0; j < g_numbones; j++) { phdr->pBone(j)->flags |= g_bonetable[j].flags; } ALIGN4( pData ); // write zero frames for (i = 0; i < g_numani; i++) { s_animation_t *anim = g_panimation[ i ]; if (panimdesc[i].animblock != 0) { panimdesc[i].zeroframeindex = pData - (byte *)&panimdesc[i]; int k = min( panimdesc[i].numframes - 1, 9 ); if (panimdesc[i].flags & STUDIO_LOOPING) { k = min( (panimdesc[i].numframes - 1) / 2, k ); } panimdesc[i].zeroframespan = k; if (k > 2) { panimdesc[i].zeroframecount = min( (panimdesc[i].numframes - 1) / panimdesc[i].zeroframespan, 3 ); // save frames 0..24 frames } if (panimdesc[i].zeroframecount < 1) panimdesc[i].zeroframecount = 1; for (j = 0; j < g_numbones; j++) { if (g_bonetable[j].flags & BONE_HAS_SAVEFRAME_POS) { for (int n = 0; n < panimdesc[i].zeroframecount; n++) { *(Vector48 *)pData = anim->sanim[panimdesc[i].zeroframespan*n][j].pos; pData += sizeof( Vector48 ); } } if (g_bonetable[j].flags & BONE_HAS_SAVEFRAME_ROT) { for (int n = 0; n < panimdesc[i].zeroframecount; n++) { Quaternion q; AngleQuaternion( anim->sanim[panimdesc[i].zeroframespan*n][j].rot, q ); *((Quaternion64 *)pData) = q; pData += sizeof( Quaternion64 ); } } } } } ALIGN4( pData ); return pData; } static void WriteTextures( studiohdr_t *phdr ) { int i, j; short *pref; // save texture info mstudiotexture_t *ptexture = (mstudiotexture_t *)pData; phdr->numtextures = g_nummaterials; phdr->textureindex = pData - pStart; pData += g_nummaterials * sizeof( mstudiotexture_t ); for (i = 0; i < g_nummaterials; i++) { j = g_material[i]; AddToStringTable( &ptexture[i], &ptexture[i].sznameindex, g_texture[j].name ); } ALIGN4( pData ); int *cdtextureoffset = (int *)pData; phdr->numcdtextures = numcdtextures; phdr->cdtextureindex = pData - pStart; pData += numcdtextures * sizeof( int ); for (i = 0; i < numcdtextures; i++) { AddToStringTable( phdr, &cdtextureoffset[i], cdtextures[i] ); } ALIGN4( pData ); // save texture directory info phdr->skinindex = (pData - pStart); phdr->numskinref = g_numskinref; phdr->numskinfamilies = g_numskinfamilies; pref = (short *)pData; for (i = 0; i < phdr->numskinfamilies; i++) { for (j = 0; j < phdr->numskinref; j++) { *pref = g_skinref[i][j]; pref++; } } pData = (byte *)pref; ALIGN4( pData ); } //----------------------------------------------------------------------------- // Write source bone transforms //----------------------------------------------------------------------------- static void WriteBoneTransforms( studiohdr2_t *phdr, mstudiobone_t *pBone ) { matrix3x4_t identity; SetIdentityMatrix( identity ); int nTransformCount = 0; for (int i = 0; i < g_numbones; i++) { if ( g_bonetable[i].flags & BONE_ALWAYS_PROCEDURAL ) continue; int nParent = g_bonetable[i].parent; // Transformation is necessary if either you or your parent was realigned if ( MatricesAreEqual( identity, g_bonetable[i].srcRealign ) && ( ( nParent < 0 ) || MatricesAreEqual( identity, g_bonetable[nParent].srcRealign ) ) ) continue; ++nTransformCount; } // save bone transform info mstudiosrcbonetransform_t *pSrcBoneTransform = (mstudiosrcbonetransform_t *)pData; phdr->numsrcbonetransform = nTransformCount; phdr->srcbonetransformindex = pData - pStart; pData += nTransformCount * sizeof( mstudiosrcbonetransform_t ); int bt = 0; for ( int i = 0; i < g_numbones; i++ ) { if ( g_bonetable[i].flags & BONE_ALWAYS_PROCEDURAL ) continue; int nParent = g_bonetable[i].parent; if ( MatricesAreEqual( identity, g_bonetable[i].srcRealign ) && ( ( nParent < 0 ) || MatricesAreEqual( identity, g_bonetable[nParent].srcRealign ) ) ) continue; // What's going on here? // So, when we realign a bone, we want to do it in a way so that the child bones // have the same bone->world transform. If we take T as the src realignment transform // for the parent, P is the parent to world, and C is the child to parent, we expect // the child->world is constant after realignment: // CtoW = P * C = ( P * T ) * ( T^-1 * C ) // therefore Cnew = ( T^-1 * C ) if ( nParent >= 0 ) { MatrixInvert( g_bonetable[nParent].srcRealign, pSrcBoneTransform[bt].pretransform ); } else { SetIdentityMatrix( pSrcBoneTransform[bt].pretransform ); } MatrixCopy( g_bonetable[i].srcRealign, pSrcBoneTransform[bt].posttransform ); AddToStringTable( &pSrcBoneTransform[bt], &pSrcBoneTransform[bt].sznameindex, g_bonetable[i].name ); ++bt; } ALIGN4( pData ); if (g_numbones > 1) { // write second bone table phdr->linearboneindex = pData - (byte *)phdr; mstudiolinearbone_t *pLinearBone = (mstudiolinearbone_t *)pData; pData += sizeof( *pLinearBone ); pLinearBone->numbones = g_numbones; #define WRITE_BONE_BLOCK( type, srcfield, dest, destindex ) \ type *##dest = (type *)pData; \ pLinearBone->##destindex = pData - (byte *)pLinearBone; \ pData += g_numbones * sizeof( *##dest ); \ ALIGN4( pData ); \ for ( int i = 0; i < g_numbones; i++) \ dest##[i] = pBone[i].##srcfield; WRITE_BONE_BLOCK( int, flags, pFlags, flagsindex ); WRITE_BONE_BLOCK( int, parent, pParent, parentindex ); WRITE_BONE_BLOCK( Vector, pos, pPos, posindex ); WRITE_BONE_BLOCK( Quaternion, quat, pQuat, quatindex ); WRITE_BONE_BLOCK( RadianEuler, rot, pRot, rotindex ); WRITE_BONE_BLOCK( matrix3x4_t, poseToBone, pPoseToBone, posetoboneindex ); WRITE_BONE_BLOCK( Vector, posscale, pPoseScale, posscaleindex ); WRITE_BONE_BLOCK( Vector, rotscale, pRotScale, rotscaleindex ); WRITE_BONE_BLOCK( Quaternion, qAlignment, pQAlignment, qalignmentindex ); } } //----------------------------------------------------------------------------- // Write the bone flex drivers //----------------------------------------------------------------------------- static void WriteBoneFlexDrivers( studiohdr2_t *pStudioHdr2 ) { ALIGN4( pData ); pStudioHdr2->m_nBoneFlexDriverCount = 0; pStudioHdr2->m_nBoneFlexDriverIndex = 0; CDmeBoneFlexDriverList *pDmeBoneFlexDriverList = GetElement< CDmeBoneFlexDriverList >( g_hDmeBoneFlexDriverList ); if ( !pDmeBoneFlexDriverList ) return; const int nBoneFlexDriverCount = pDmeBoneFlexDriverList->m_eBoneFlexDriverList.Count(); if ( nBoneFlexDriverCount <= 0 ) return; mstudioboneflexdriver_t *pBoneFlexDriver = (mstudioboneflexdriver_t *)pData; pStudioHdr2->m_nBoneFlexDriverCount = nBoneFlexDriverCount; pStudioHdr2->m_nBoneFlexDriverIndex = pData - (byte *)pStudioHdr2; pData += nBoneFlexDriverCount * sizeof( mstudioboneflexdriver_t ); ALIGN4( pData ); for ( int i = 0; i < nBoneFlexDriverCount; ++i ) { CDmeBoneFlexDriver *pDmeBoneFlexDriver = pDmeBoneFlexDriverList->m_eBoneFlexDriverList[i]; Assert( pDmeBoneFlexDriver ); Assert( pDmeBoneFlexDriver->m_eControlList.Count() > 0 ); Assert( pDmeBoneFlexDriver->GetValue< int >( "__boneIndex", -1 ) >= 0 ); pBoneFlexDriver->m_nBoneIndex = pDmeBoneFlexDriver->GetValue< int >( "__boneIndex", 0 ); pBoneFlexDriver->m_nControlCount = pDmeBoneFlexDriver->m_eControlList.Count(); pBoneFlexDriver->m_nControlIndex = pData - (byte *)pBoneFlexDriver; mstudioboneflexdrivercontrol_t *pControl = reinterpret_cast< mstudioboneflexdrivercontrol_t * >( pData ); for ( int j = 0; j < pBoneFlexDriver->m_nControlCount; ++j ) { CDmeBoneFlexDriverControl *pDmeControl = pDmeBoneFlexDriver->m_eControlList[j]; Assert( pDmeControl ); Assert( pDmeControl->GetValue< int >( "__flexControlIndex", -1 ) >= 0 ); Assert( pDmeControl->m_nBoneComponent >= STUDIO_BONE_FLEX_TX ); Assert( pDmeControl->m_nBoneComponent <= STUDIO_BONE_FLEX_TZ ); pControl[j].m_nFlexControllerIndex = pDmeControl->GetValue< int >( "__flexControlIndex", 0 ); pControl[j].m_nBoneComponent = pDmeControl->m_nBoneComponent; pControl[j].m_flMin = pDmeControl->m_flMin; pControl[j].m_flMax = pDmeControl->m_flMax; } pData += pBoneFlexDriver->m_nControlCount * sizeof( mstudioboneflexdrivercontrol_t ); ALIGN4( pData ); ++pBoneFlexDriver; } } //----------------------------------------------------------------------------- // Write the processed vertices //----------------------------------------------------------------------------- static void WriteVertices( studiohdr_t *phdr ) { char fileName[MAX_PATH]; byte *pStart; byte *pData; int i; int j; int k; int cur; if (!g_nummodelsbeforeLOD) return; V_strcpy_safe( fileName, gamedir ); // if( *g_pPlatformName ) // { // strcat( fileName, "platform_" ); // strcat( fileName, g_pPlatformName ); // strcat( fileName, "/" ); // } V_strcat_safe( fileName, "models/" ); V_strcat_safe( fileName, outname ); Q_StripExtension( fileName, fileName, sizeof( fileName ) ); V_strcat_safe( fileName, ".vvd" ); if ( !g_quiet ) { printf ("---------------------\n"); printf ("writing %s:\n", fileName); } pStart = (byte *)kalloc( 1, FILEBUFFER ); pData = pStart; vertexFileHeader_t *fileHeader = (vertexFileHeader_t *)pData; pData += sizeof(vertexFileHeader_t); fileHeader->id = MODEL_VERTEX_FILE_ID; fileHeader->version = MODEL_VERTEX_FILE_VERSION; fileHeader->checksum = phdr->checksum; // data has no fixes and requires no fixes fileHeader->numFixups = 0; fileHeader->fixupTableStart = 0; // unfinalized during first pass, fixed during second pass // data can be considered as single lod at lod 0 fileHeader->numLODs = 1; fileHeader->numLODVertexes[0] = 0; // store vertexes grouped by mesh order ALIGN16( pData ); fileHeader->vertexDataStart = pData-pStart; for (i = 0; i < g_nummodelsbeforeLOD; i++) { s_loddata_t *pLodData = g_model[i]->m_pLodData; // skip blank empty model if (!pLodData) continue; // save vertices ALIGN16( pData ); cur = (int)pData; mstudiovertex_t *pVert = (mstudiovertex_t *)pData; pData += pLodData->numvertices * sizeof( mstudiovertex_t ); for (j = 0; j < pLodData->numvertices; j++) { // printf( "saving bone weight %d for model %d at 0x%p\n", // j, i, &pbone[j] ); const s_vertexinfo_t &lodVertex = pLodData->vertex[j]; VectorCopy( lodVertex.position, pVert[j].m_vecPosition ); VectorCopy( lodVertex.normal, pVert[j].m_vecNormal ); Vector2DCopy( lodVertex.texcoord, pVert[j].m_vecTexCoord ); mstudioboneweight_t *pBoneWeight = &pVert[j].m_BoneWeights; memset( pBoneWeight, 0, sizeof( mstudioboneweight_t ) ); pBoneWeight->numbones = lodVertex.boneweight.numbones; for (k = 0; k < pBoneWeight->numbones; k++) { pBoneWeight->bone[k] = lodVertex.boneweight.bone[k]; pBoneWeight->weight[k] = lodVertex.boneweight.weight[k]; } } fileHeader->numLODVertexes[0] += pLodData->numvertices; if (!g_quiet) { printf( "vertices %7d bytes (%d vertices)\n", (int)(pData - cur), pLodData->numvertices ); } } // store tangents grouped by mesh order ALIGN4( pData ); fileHeader->tangentDataStart = pData-pStart; for (i = 0; i < g_nummodelsbeforeLOD; i++) { s_loddata_t *pLodData = g_model[i]->m_pLodData; // skip blank empty model if (!pLodData) continue; // save tangent space S ALIGN4( pData ); cur = (int)pData; Vector4D *ptangents = (Vector4D *)pData; pData += pLodData->numvertices * sizeof( Vector4D ); for (j = 0; j < pLodData->numvertices; j++) { Vector4DCopy( pLodData->vertex[j].tangentS, ptangents[j] ); #ifdef _DEBUG float w = ptangents[j].w; Assert( w == 1.0f || w == -1.0f ); #endif } if (!g_quiet) { printf( "tangents %7d bytes (%d vertices)\n", (int)(pData - cur), pLodData->numvertices ); } } if (!g_quiet) { printf( "total %7d bytes\n", pData - pStart ); } // fileHeader->length = pData - pStart; { CP4AutoEditAddFile autop4( fileName, "binary" ); SaveFile( fileName, pStart, pData - pStart ); } } //----------------------------------------------------------------------------- // Computes the maximum absolute value of any component of all vertex animation // pos (x,y,z) normal (x,y,z) or wrinkle // // Returns the fixed point scale and also sets appropriate values & flags in // passed studiohdr_t //----------------------------------------------------------------------------- float ComputeVertAnimFixedPointScale( studiohdr_t *pStudioHdr ) { float flVertAnimRange = 0.0f; for ( int j = 0; j < g_numflexkeys; ++j ) { if ( g_flexkey[j].numvanims <= 0 ) continue; const bool bWrinkleVAnim = ( g_flexkey[j].vanimtype == STUDIO_VERT_ANIM_WRINKLE ); s_vertanim_t *pVertAnim = g_flexkey[j].vanim; for ( int k = 0; k < g_flexkey[j].numvanims; ++k ) { if ( fabs( pVertAnim->pos.x ) > flVertAnimRange ) { flVertAnimRange = fabs( pVertAnim->pos.x ); } if ( fabs( pVertAnim->pos.y ) > flVertAnimRange ) { flVertAnimRange = fabs( pVertAnim->pos.y ); } if ( fabs( pVertAnim->pos.z ) > flVertAnimRange ) { flVertAnimRange = fabs( pVertAnim->pos.z ); } if ( fabs( pVertAnim->normal.x ) > flVertAnimRange ) { flVertAnimRange = fabs( pVertAnim->normal.x ); } if ( fabs( pVertAnim->normal.y ) > flVertAnimRange ) { flVertAnimRange = fabs( pVertAnim->normal.y ); } if ( fabs( pVertAnim->normal.z ) > flVertAnimRange ) { flVertAnimRange = fabs( pVertAnim->normal.z ); } if ( bWrinkleVAnim ) { if ( fabs( pVertAnim->wrinkle ) > flVertAnimRange ) { flVertAnimRange = fabs( pVertAnim->wrinkle ); } } pVertAnim++; } } // Legacy value float flVertAnimFixedPointScale = 1.0 / 4096.0f; if ( flVertAnimRange > 0.0f ) { if ( flVertAnimRange > 32767 ) { MdlWarning( "Flex value too large: %.2f, Max: 32767\n", flVertAnimRange ); flVertAnimFixedPointScale = 1.0f; } else { const float flTmpScale = flVertAnimRange / 32767.0f; if ( flTmpScale > flVertAnimFixedPointScale ) { flVertAnimFixedPointScale = flTmpScale; } } } if ( flVertAnimFixedPointScale != 1.0f / 4096.0f ) { pStudioHdr->flags |= STUDIOHDR_FLAGS_VERT_ANIM_FIXED_POINT_SCALE; pStudioHdr->flVertAnimFixedPointScale = flVertAnimFixedPointScale; } return flVertAnimFixedPointScale; } static void WriteModel( studiohdr_t *phdr ) { int i, j, k, m; mstudiobodyparts_t *pbodypart; mstudiomodel_t *pmodel; s_source_t *psource; mstudiovertanim_t *pvertanim; s_vertanim_t *pvanim; int cur = (int)pData; // vertex data is written to external file, offsets kept internal // track expected external base to store proper offsets byte *externalVertexIndex = 0; byte *externalTangentsIndex = 0; // write bodypart info pbodypart = (mstudiobodyparts_t *)pData; phdr->numbodyparts = g_numbodyparts; phdr->bodypartindex = pData - pStart; pData += g_numbodyparts * sizeof( mstudiobodyparts_t ); pmodel = (mstudiomodel_t *)pData; pData += g_nummodelsbeforeLOD * sizeof( mstudiomodel_t ); for (i = 0, j = 0; i < g_numbodyparts; i++) { AddToStringTable( &pbodypart[i], &pbodypart[i].sznameindex, g_bodypart[i].name ); pbodypart[i].nummodels = g_bodypart[i].nummodels; pbodypart[i].base = g_bodypart[i].base; pbodypart[i].modelindex = ((byte *)&pmodel[j]) - (byte *)&pbodypart[i]; j += g_bodypart[i].nummodels; } ALIGN4( pData ); // write global flex names mstudioflexdesc_t *pflexdesc = (mstudioflexdesc_t *)pData; phdr->numflexdesc = g_numflexdesc; phdr->flexdescindex = pData - pStart; pData += g_numflexdesc * sizeof( mstudioflexdesc_t ); ALIGN4( pData ); for (j = 0; j < g_numflexdesc; j++) { // printf("%d %s\n", j, g_flexdesc[j].FACS ); AddToStringTable( pflexdesc, &pflexdesc->szFACSindex, g_flexdesc[j].FACS ); pflexdesc++; } // write global flex controllers mstudioflexcontroller_t *pflexcontroller = (mstudioflexcontroller_t *)pData; phdr->numflexcontrollers = g_numflexcontrollers; phdr->flexcontrollerindex = pData - pStart; pData += g_numflexcontrollers * sizeof( mstudioflexcontroller_t ); ALIGN4( pData ); for (j = 0; j < g_numflexcontrollers; j++) { AddToStringTable( pflexcontroller, &pflexcontroller->sznameindex, g_flexcontroller[j].name ); AddToStringTable( pflexcontroller, &pflexcontroller->sztypeindex, g_flexcontroller[j].type ); pflexcontroller->min = g_flexcontroller[j].min; pflexcontroller->max = g_flexcontroller[j].max; pflexcontroller->localToGlobal = -1; pflexcontroller++; } // write flex rules mstudioflexrule_t *pflexrule = (mstudioflexrule_t *)pData; phdr->numflexrules = g_numflexrules; phdr->flexruleindex = pData - pStart; pData += g_numflexrules * sizeof( mstudioflexrule_t ); ALIGN4( pData ); for (j = 0; j < g_numflexrules; j++) { pflexrule->flex = g_flexrule[j].flex; pflexrule->numops = g_flexrule[j].numops; pflexrule->opindex = (pData - (byte *)pflexrule); mstudioflexop_t *pflexop = (mstudioflexop_t *)pData; for (i = 0; i < pflexrule->numops; i++) { pflexop[i].op = g_flexrule[j].op[i].op; pflexop[i].d.index = g_flexrule[j].op[i].d.index; } pData += sizeof( mstudioflexop_t ) * pflexrule->numops; ALIGN4( pData ); pflexrule++; } // write global flex controller information mstudioflexcontrollerui_t *pFlexControllerUI = (mstudioflexcontrollerui_t *)pData; phdr->numflexcontrollerui = 0; phdr->flexcontrolleruiindex = pData - pStart; // Loop through all defined controllers and create a UI structure for them // All actual controllers will be defined as a member of some ui structure // and all actual controllers can only be a member of one ui structure bool *pControllerHandled = ( bool * )_alloca( g_numflexcontrollers * sizeof( bool ) ); memset( pControllerHandled, 0, g_numflexcontrollers * sizeof( bool ) ); for ( j = 0; j < g_numflexcontrollers; ++j ) { // Don't handle controls twice if ( pControllerHandled[ j ] ) continue; const s_flexcontroller_t &flexcontroller = g_flexcontroller[ j ]; bool found = false; // See if this controller is in the remap table for ( k = 0; k < g_FlexControllerRemap.Count(); ++k ) { s_flexcontrollerremap_t &remap = g_FlexControllerRemap[ k ]; if ( j == remap.m_Index || j == remap.m_LeftIndex || j == remap.m_RightIndex || j == remap.m_MultiIndex ) { AddToStringTable( pFlexControllerUI, &pFlexControllerUI->sznameindex, remap.m_Name ); pFlexControllerUI->stereo = remap.m_bIsStereo; if ( pFlexControllerUI->stereo ) { Assert( !pControllerHandled[ remap.m_LeftIndex ] ); pFlexControllerUI->szindex0 = ( phdr->flexcontrollerindex - int( pData - pStart ) + remap.m_LeftIndex * sizeof( mstudioflexcontroller_t ) ); pControllerHandled[ remap.m_LeftIndex ] = true; Assert( !pControllerHandled[ remap.m_RightIndex ] ); pFlexControllerUI->szindex1 = ( phdr->flexcontrollerindex - int( pData - pStart ) + remap.m_RightIndex * sizeof( mstudioflexcontroller_t ) ); pControllerHandled[ remap.m_RightIndex ] = true; } else { Assert( !pControllerHandled[ remap.m_Index ] ); pFlexControllerUI->szindex0 = ( phdr->flexcontrollerindex - int( pData - pStart ) + remap.m_Index * sizeof( mstudioflexcontroller_t ) ); pControllerHandled[ remap.m_Index ] = true; pFlexControllerUI->szindex1 = ( 0 ); } pFlexControllerUI->remaptype = remap.m_RemapType; if ( pFlexControllerUI->remaptype == FLEXCONTROLLER_REMAP_NWAY || pFlexControllerUI->remaptype == FLEXCONTROLLER_REMAP_EYELID ) { Assert( remap.m_MultiIndex != -1 ); Assert( !pControllerHandled[ remap.m_MultiIndex ] ); pFlexControllerUI->szindex2 = ( phdr->flexcontrollerindex - int( pData - pStart ) + remap.m_MultiIndex * sizeof( mstudioflexcontroller_t ) ); pControllerHandled[ remap.m_MultiIndex ] = true; } else { pFlexControllerUI->szindex2 = 0; } found = true; break; } } if ( !found ) { pFlexControllerUI->remaptype = FLEXCONTROLLER_REMAP_PASSTHRU; pFlexControllerUI->szindex2 = 0; // Unused in this case if ( j < g_numflexcontrollers - 1 && StringAfterPrefixCaseSensitive( flexcontroller.name, "right_" ) && StringAfterPrefixCaseSensitive( g_flexcontroller[ j + 1 ].name, "left_" ) && !Q_strcmp( StringAfterPrefixCaseSensitive( flexcontroller.name, "right_" ), StringAfterPrefixCaseSensitive( g_flexcontroller[ j + 1 ].name, "left_" ) ) ) { AddToStringTable( pFlexControllerUI, &pFlexControllerUI->sznameindex, flexcontroller.name + 6 ); pFlexControllerUI->stereo = true; Assert( !pControllerHandled[ j + 1 ] ); pFlexControllerUI->szindex0 = ( phdr->flexcontrollerindex - int( pData - pStart ) + ( j + 1 ) * sizeof( mstudioflexcontroller_t ) ); pControllerHandled[ j + 1 ] = true; Assert( !pControllerHandled[ j ] ); pFlexControllerUI->szindex1 = ( phdr->flexcontrollerindex - int( pData - pStart ) + j * sizeof( mstudioflexcontroller_t ) ); pControllerHandled[ j ] = true; } else if ( j > 0 && StringAfterPrefixCaseSensitive( flexcontroller.name, "left_" ) && StringAfterPrefixCaseSensitive( g_flexcontroller[ j - 1 ].name, "right_" ) && !Q_strcmp( StringAfterPrefixCaseSensitive( flexcontroller.name, "left_" ), StringAfterPrefixCaseSensitive( g_flexcontroller[ j - 1 ].name, "right_" ) ) ) { AddToStringTable( pFlexControllerUI, &pFlexControllerUI->sznameindex, flexcontroller.name + 5 ); pFlexControllerUI->stereo = true; Assert( !pControllerHandled[ j ] ); pFlexControllerUI->szindex0 = ( phdr->flexcontrollerindex - int( pData - pStart ) + j * sizeof( mstudioflexcontroller_t ) ); pControllerHandled[ j ] = true; Assert( !pControllerHandled[ j - 1 ] ); pFlexControllerUI->szindex1 = ( phdr->flexcontrollerindex - int( pData - pStart ) + ( j - 1 ) * sizeof( mstudioflexcontroller_t ) ); pControllerHandled[ j - 1 ] = true; } else { AddToStringTable( pFlexControllerUI, &pFlexControllerUI->sznameindex, flexcontroller.name ); pFlexControllerUI->stereo = false; pFlexControllerUI->szindex0 = ( phdr->flexcontrollerindex - int( pData - pStart ) + j * sizeof( mstudioflexcontroller_t ) ); pFlexControllerUI->szindex1 = 0; // Unused in this case pControllerHandled[ j ] = true; } } phdr->numflexcontrollerui++; pData += sizeof( mstudioflexcontrollerui_t ); ++pFlexControllerUI; } ALIGN4( pData ); #ifdef _DEBUG for ( j = 0; j < g_numflexcontrollers; ++j ) { Assert( pControllerHandled[ j ] ); } #endif // _DEBUG // write ik chains mstudioikchain_t *pikchain = (mstudioikchain_t *)pData; phdr->numikchains = g_numikchains; phdr->ikchainindex = pData - pStart; pData += g_numikchains * sizeof( mstudioikchain_t ); ALIGN4( pData ); for (j = 0; j < g_numikchains; j++) { AddToStringTable( pikchain, &pikchain->sznameindex, g_ikchain[j].name ); pikchain->numlinks = g_ikchain[j].numlinks; mstudioiklink_t *piklink = (mstudioiklink_t *)pData; pikchain->linkindex = (pData - (byte *)pikchain); pData += pikchain->numlinks * sizeof( mstudioiklink_t ); for (i = 0; i < pikchain->numlinks; i++) { piklink[i].bone = g_ikchain[j].link[i].bone; piklink[i].kneeDir = g_ikchain[j].link[i].kneeDir; } pikchain++; } // save autoplay locks mstudioiklock_t *piklock = (mstudioiklock_t *)pData; phdr->numlocalikautoplaylocks = g_numikautoplaylocks; phdr->localikautoplaylockindex = pData - pStart; pData += g_numikautoplaylocks * sizeof( mstudioiklock_t ); ALIGN4( pData ); for (j = 0; j < g_numikautoplaylocks; j++) { piklock->chain = g_ikautoplaylock[j].chain; piklock->flPosWeight = g_ikautoplaylock[j].flPosWeight; piklock->flLocalQWeight = g_ikautoplaylock[j].flLocalQWeight; piklock++; } // save mouth info mstudiomouth_t *pmouth = (mstudiomouth_t *)pData; phdr->nummouths = g_nummouths; phdr->mouthindex = pData - pStart; pData += g_nummouths * sizeof( mstudiomouth_t ); ALIGN4( pData ); for (i = 0; i < g_nummouths; i++) { pmouth[i].bone = g_mouth[i].bone; VectorCopy( g_mouth[i].forward, pmouth[i].forward ); pmouth[i].flexdesc = g_mouth[i].flexdesc; } // save pose parameters mstudioposeparamdesc_t *ppose = (mstudioposeparamdesc_t *)pData; phdr->numlocalposeparameters = g_numposeparameters; phdr->localposeparamindex = pData - pStart; pData += g_numposeparameters * sizeof( mstudioposeparamdesc_t ); ALIGN4( pData ); for (i = 0; i < g_numposeparameters; i++) { AddToStringTable( &ppose[i], &ppose[i].sznameindex, g_pose[i].name ); ppose[i].start = g_pose[i].min; ppose[i].end = g_pose[i].max; ppose[i].flags = g_pose[i].flags; ppose[i].loop = g_pose[i].loop; } if( !g_quiet ) { printf("ik/pose %7d bytes\n", (int)(pData - cur) ); } cur = (int)pData; const float flVertAnimFixedPointScale = ComputeVertAnimFixedPointScale( phdr ); // write model for (i = 0; i < g_nummodelsbeforeLOD; i++) { int n = 0; byte *pModelStart = (byte *)(&pmodel[i]); strcpy( pmodel[i].name, g_model[i]->filename ); // AddToStringTable( &pmodel[i], &pmodel[i].sznameindex, g_model[i]->filename ); // pmodel[i].mrmbias = g_model[i]->mrmbias; // pmodel[i].minresolution = g_model[i]->minresolution; // pmodel[i].maxresolution = g_model[i]->maxresolution; // save bbox info psource = g_model[i]->source; s_loddata_t *pLodData = g_model[i]->m_pLodData; // save mesh info if (pLodData) { pmodel[i].numvertices = pLodData->numvertices; } else { // empty model pmodel[i].numvertices = 0; } if ( pmodel[i].numvertices >= MAXSTUDIOVERTS ) { // We have to check this here so that we don't screw up decal // vert caching in the runtime. MdlError( "Too many verts in model. (%d verts, MAXSTUDIOVERTS==%d)\n", pmodel[i].numvertices, ( int )MAXSTUDIOVERTS ); } mstudiomesh_t *pmesh = (mstudiomesh_t *)pData; pmodel[i].meshindex = (pData - pModelStart); pData += psource->nummeshes * sizeof( mstudiomesh_t ); ALIGN4( pData ); pmodel[i].nummeshes = psource->nummeshes; for (m = 0; m < pmodel[i].nummeshes; m++) { n = psource->meshindex[m]; pmesh[m].material = n; pmesh[m].modelindex = (byte *)&pmodel[i] - (byte *)&pmesh[m]; pmesh[m].numvertices = pLodData->mesh[n].numvertices; pmesh[m].vertexoffset = pLodData->mesh[n].vertexoffset; } // set expected base offsets to external data ALIGN16( externalVertexIndex ); pmodel[i].vertexindex = (int)externalVertexIndex; externalVertexIndex += pmodel[i].numvertices * sizeof(mstudiovertex_t); // set expected base offsets to external data ALIGN4( externalTangentsIndex ); pmodel[i].tangentsindex = (int)externalTangentsIndex; externalTangentsIndex += pmodel[i].numvertices * sizeof( Vector4D ); cur = (int)pData; // save eyeballs mstudioeyeball_t *peyeball; peyeball = (mstudioeyeball_t *)pData; pmodel[i].numeyeballs = g_model[i]->numeyeballs; pmodel[i].eyeballindex = pData - pModelStart; pData += g_model[i]->numeyeballs * sizeof( mstudioeyeball_t ); ALIGN4( pData ); for (j = 0; j < g_model[i]->numeyeballs; j++) { k = g_model[i]->eyeball[j].mesh; pmesh[k].materialtype = 1; // FIXME: tag custom material pmesh[k].materialparam = j; // FIXME: tag custom material peyeball[j].bone = g_model[i]->eyeball[j].bone; VectorCopy( g_model[i]->eyeball[j].org, peyeball[j].org ); peyeball[j].zoffset = g_model[i]->eyeball[j].zoffset; peyeball[j].radius = g_model[i]->eyeball[j].radius; VectorCopy( g_model[i]->eyeball[j].up, peyeball[j].up ); VectorCopy( g_model[i]->eyeball[j].forward, peyeball[j].forward ); peyeball[j].iris_scale = g_model[i]->eyeball[j].iris_scale; for (k = 0; k < 3; k++) { peyeball[j].upperflexdesc[k] = g_model[i]->eyeball[j].upperflexdesc[k]; peyeball[j].lowerflexdesc[k] = g_model[i]->eyeball[j].lowerflexdesc[k]; peyeball[j].uppertarget[k] = g_model[i]->eyeball[j].uppertarget[k]; peyeball[j].lowertarget[k] = g_model[i]->eyeball[j].lowertarget[k]; } peyeball[j].upperlidflexdesc = g_model[i]->eyeball[j].upperlidflexdesc; peyeball[j].lowerlidflexdesc = g_model[i]->eyeball[j].lowerlidflexdesc; } if ( !g_quiet ) { printf("eyeballs %7d bytes (%d eyeballs)\n", (int)(pData - cur), g_model[i]->numeyeballs ); } // move flexes into individual meshes cur = (int)pData; for (m = 0; m < pmodel[i].nummeshes; m++) { int numflexkeys[MAXSTUDIOFLEXKEYS]; pmesh[m].numflexes = 0; // initialize array for (j = 0; j < g_numflexkeys; j++) { numflexkeys[j] = 0; } // count flex instances per mesh for (j = 0; j < g_numflexkeys; j++) { if (g_flexkey[j].imodel == i) { for (k = 0; k < g_flexkey[j].numvanims; k++) { n = g_flexkey[j].vanim[k].vertex - pmesh[m].vertexoffset; if (n >= 0 && n < pmesh[m].numvertices) { if (numflexkeys[j]++ == 0) { pmesh[m].numflexes++; } } } } } if (pmesh[m].numflexes) { pmesh[m].flexindex = ( pData - (byte *)&pmesh[m] ); mstudioflex_t *pflex = (mstudioflex_t *)pData; pData += pmesh[m].numflexes * sizeof( mstudioflex_t ); ALIGN4( pData ); for (j = 0; j < g_numflexkeys; j++) { if (!numflexkeys[j]) continue; pflex->flexdesc = g_flexkey[j].flexdesc; pflex->target0 = g_flexkey[j].target0; pflex->target1 = g_flexkey[j].target1; pflex->target2 = g_flexkey[j].target2; pflex->target3 = g_flexkey[j].target3; pflex->numverts = numflexkeys[j]; pflex->vertindex = (pData - (byte *)pflex); pflex->flexpair = g_flexkey[j].flexpair; pflex->vertanimtype = g_flexkey[j].vanimtype; // printf("%d %d %s : %f %f %f %f\n", j, g_flexkey[j].flexdesc, g_flexdesc[g_flexkey[j].flexdesc].FACS, g_flexkey[j].target0, g_flexkey[j].target1, g_flexkey[j].target2, g_flexkey[j].target3 ); // if (j < 9) printf("%d %d %s : %d (%d) %f\n", j, g_flexkey[j].flexdesc, g_flexdesc[g_flexkey[j].flexdesc].FACS, g_flexkey[j].numvanims, pflex->numverts, g_flexkey[j].target ); // printf("%d %d : %d %f\n", j, g_flexkey[j].flexnum, g_flexkey[j].numvanims, g_flexkey[j].target ); pvanim = g_flexkey[j].vanim; bool bWrinkleVAnim = ( pflex->vertanimtype == STUDIO_VERT_ANIM_WRINKLE ); int nVAnimDeltaSize = bWrinkleVAnim ? sizeof(mstudiovertanim_wrinkle_t) : sizeof(mstudiovertanim_t); pvertanim = (mstudiovertanim_t *)pData; pData += pflex->numverts * nVAnimDeltaSize; ALIGN4( pData ); for ( k = 0; k < g_flexkey[j].numvanims; k++ ) { n = g_flexkey[j].vanim[k].vertex - pmesh[m].vertexoffset; if ( n >= 0 && n < pmesh[m].numvertices ) { pvertanim->index = n; pvertanim->speed = 255.0F*pvanim->speed; pvertanim->side = 255.0F*pvanim->side; pvertanim->SetDeltaFloat( pvanim->pos ); pvertanim->SetNDeltaFloat( pvanim->normal ); if ( bWrinkleVAnim ) { ( (mstudiovertanim_wrinkle_t*)pvertanim )->SetWrinkleFixed( pvanim->wrinkle, flVertAnimFixedPointScale ); } pvertanim = (mstudiovertanim_t*)( (byte*)pvertanim + nVAnimDeltaSize ); /* if ((tmp - pvanim->pos).Length() > 0.1) { pvertanim->delta.x = pvanim->pos.x; printf("%f %f %f : %f %f %f\n", pvanim->pos[0], pvanim->pos[1], pvanim->pos[2], tmp.x, tmp.y, tmp.z ); } */ // if (j < 9) printf("%d %.2f %.2f %.2f\n", n, pvanim->pos[0], pvanim->pos[1], pvanim->pos[2] ); } // printf("%d %.2f %.2f %.2f\n", pvanim->vertex, pvanim->pos[0], pvanim->pos[1], pvanim->pos[2] ); pvanim++; } pflex++; } } } if( !g_quiet ) { printf("flexes %7d bytes (%d flexes)\n", (int)(pData - cur), g_numflexkeys ); } cur = (int)pData; } ALIGN4( pData ); mstudiomodelgroup_t *pincludemodel = (mstudiomodelgroup_t *)pData; phdr->numincludemodels = g_numincludemodels; phdr->includemodelindex = pData - pStart; pData += g_numincludemodels * sizeof( mstudiomodelgroup_t ); for (i = 0; i < g_numincludemodels; i++) { AddToStringTable( pincludemodel, &pincludemodel->sznameindex, g_includemodel[i].name ); pincludemodel++; } // save animblock group info mstudioanimblock_t *panimblock = (mstudioanimblock_t *)pData; phdr->numanimblocks = g_numanimblocks; phdr->animblockindex = pData - pStart; pData += phdr->numanimblocks * sizeof( mstudioanimblock_t ); ALIGN4( pData ); for (i = 1; i < g_numanimblocks; i++) { panimblock[i].datastart = g_animblock[i].start - pBlockStart; panimblock[i].dataend = g_animblock[i].end - pBlockStart; // printf("block %d : %x %x (%d)\n", i, panimblock[i].datastart, panimblock[i].dataend, panimblock[i].dataend - panimblock[i].datastart ); } AddToStringTable( phdr, &phdr->szanimblocknameindex, g_animblockname ); } static void AssignMeshIDs( studiohdr_t *pStudioHdr ) { int i; int j; int m; int numMeshes; mstudiobodyparts_t *pStudioBodyPart; mstudiomodel_t *pStudioModel; mstudiomesh_t *pStudioMesh; numMeshes = 0; for (i=0; inumbodyparts; i++) { pStudioBodyPart = pStudioHdr->pBodypart(i); for (j=0; jnummodels; j++) { pStudioModel = pStudioBodyPart->pModel(j); for (m=0; mnummeshes; m++) { // get each mesh pStudioMesh = pStudioModel->pMesh(m); pStudioMesh->meshid = numMeshes + m; } numMeshes += pStudioModel->nummeshes; } } } void LoadMaterials( studiohdr_t *phdr ) { int i, j; // get index of each material if( phdr->textureindex != 0 ) { for( i = 0; i < phdr->numtextures; i++ ) { char szPath[256]; IMaterial *pMaterial = NULL; // search through all specified directories until a valid material is found for( j = 0; j < phdr->numcdtextures && IsErrorMaterial( pMaterial ); j++ ) { strcpy( szPath, phdr->pCdtexture( j ) ); strcat( szPath, phdr->pTexture( i )->pszName( ) ); pMaterial = g_pMaterialSystem->FindMaterial( szPath, TEXTURE_GROUP_OTHER, false ); } if( IsErrorMaterial( pMaterial ) && !g_quiet ) { // hack - if it isn't found, go through the motions of looking for it again // so that the materialsystem will give an error. for( j = 0; j < phdr->numcdtextures; j++ ) { strcpy( szPath, phdr->pCdtexture( j ) ); strcat( szPath, phdr->pTexture( i )->pszName( ) ); g_pMaterialSystem->FindMaterial( szPath, TEXTURE_GROUP_OTHER, true ); } } phdr->pTexture( i )->material = pMaterial; // FIXME: hack, needs proper client side material system interface bool found = false; IMaterialVar *clientShaderVar = pMaterial->FindVar( "$clientShader", &found, false ); if( found ) { if (stricmp( clientShaderVar->GetStringValue(), "MouthShader") == 0) { phdr->pTexture( i )->flags = 1; } phdr->pTexture( i )->used = 1; } } } } void WriteKeyValues( studiohdr_t *phdr, CUtlVector< char > *pKeyValue ) { phdr->keyvalueindex = (pData - pStart); phdr->keyvaluesize = KeyValueTextSize( pKeyValue ); if (phdr->keyvaluesize) { memcpy( pData, KeyValueText( pKeyValue ), phdr->keyvaluesize ); // Add space for a null terminator pData[phdr->keyvaluesize] = 0; ++phdr->keyvaluesize; pData += phdr->keyvaluesize * sizeof( char ); } ALIGN4( pData ); } void WriteSeqKeyValues( mstudioseqdesc_t *pseqdesc, CUtlVector< char > *pKeyValue ) { pseqdesc->keyvalueindex = (pData - (byte *)pseqdesc); pseqdesc->keyvaluesize = KeyValueTextSize( pKeyValue ); if (pseqdesc->keyvaluesize) { memcpy( pData, KeyValueText( pKeyValue ), pseqdesc->keyvaluesize ); // Add space for a null terminator pData[pseqdesc->keyvaluesize] = 0; ++pseqdesc->keyvaluesize; pData += pseqdesc->keyvaluesize * sizeof( char ); } ALIGN4( pData ); } void EnsureFileDirectoryExists( const char *pFilename ) { char dirName[MAX_PATH]; Q_strncpy( dirName, pFilename, sizeof( dirName ) ); Q_FixSlashes( dirName ); char *pLastSlash = strrchr( dirName, CORRECT_PATH_SEPARATOR ); if ( pLastSlash ) { *pLastSlash = 0; if ( _access( dirName, 0 ) != 0 ) { char cmdLine[512]; Q_snprintf( cmdLine, sizeof( cmdLine ), "md \"%s\"", dirName ); system( cmdLine ); } } } void WriteModelFiles(void) { FileHandle_t modelouthandle = 0; FileHandle_t blockouthandle = 0; CPlainAutoPtr< CP4File > spFileBlockOut, spFileModelOut; int total = 0; int i; char filename[MAX_PATH]; studiohdr_t *phdr; studiohdr_t *pblockhdr = 0; pStart = (byte *)kalloc( 1, FILEBUFFER ); pBlockData = NULL; pBlockStart = NULL; Q_StripExtension( outname, outname, sizeof( outname ) ); if (g_animblocksize != 0) { // write the non-default g_sequence group data to separate files sprintf( g_animblockname, "models/%s.ani", outname ); V_strcpy_safe( filename, gamedir ); V_strcat_safe( filename, g_animblockname ); EnsureFileDirectoryExists( filename ); if (!g_bVerifyOnly) { spFileBlockOut.Attach( g_p4factory->AccessFile( filename ) ); spFileBlockOut->Edit(); // Create the directory hierarchy for the ANI char parentdir[MAX_PATH]; V_strcpy_safe( parentdir, filename ); V_StripFilename( parentdir ); g_pFullFileSystem->CreateDirHierarchy( parentdir ); blockouthandle = SafeOpenWrite( filename ); } pBlockStart = (byte *)kalloc( 1, FILEBUFFER ); pBlockData = pBlockStart; pblockhdr = (studiohdr_t *)pBlockData; pblockhdr->id = IDSTUDIOANIMGROUPHEADER; pblockhdr->version = STUDIO_VERSION; pBlockData += sizeof( *pblockhdr ); } // // write the g_model output file // phdr = (studiohdr_t *)pStart; phdr->id = IDSTUDIOHEADER; phdr->version = STUDIO_VERSION; V_strcat_safe (outname, ".mdl"); // strcpy( outname, ExpandPath( outname ) ); V_strcpy_safe( filename, gamedir ); // if( *g_pPlatformName ) // { // strcat( filename, "platform_" ); // strcat( filename, g_pPlatformName ); // strcat( filename, "/" ); // } V_strcat_safe( filename, "models/" ); V_strcat_safe( filename, outname ); // Create the directory. EnsureFileDirectoryExists( filename ); if( !g_quiet ) { printf ("---------------------\n"); printf ("writing %s:\n", filename); } LoadPreexistingSequenceOrder( filename ); if (!g_bVerifyOnly) { spFileModelOut.Attach( g_p4factory->AccessFile( filename ) ); spFileModelOut->Edit(); // Create the directory hierarchy for the MDL char parentdir[MAX_PATH]; V_strcpy_safe( parentdir, filename ); V_StripFilename( parentdir ); g_pFullFileSystem->CreateDirHierarchy( parentdir ); modelouthandle = SafeOpenWrite (filename); } phdr->eyeposition = eyeposition; phdr->illumposition = illumposition; if ( !g_wrotebbox && g_sequence.Count() > 0) { VectorCopy( g_sequence[0].bmin, bbox[0] ); VectorCopy( g_sequence[0].bmax, bbox[1] ); CollisionModel_ExpandBBox( bbox[0], bbox[1] ); VectorCopy( bbox[0], g_sequence[0].bmin ); VectorCopy( bbox[1], g_sequence[0].bmax ); } if ( !g_wrotecbox ) { // no default clipping box, just use per-sequence box VectorCopy( vec3_origin, cbox[0] ); VectorCopy( vec3_origin, cbox[1] ); } phdr->hull_min = bbox[0]; phdr->hull_max = bbox[1]; phdr->view_bbmin = cbox[0]; phdr->view_bbmax = cbox[1]; phdr->flags = gflags; phdr->mass = GetCollisionModelMass(); phdr->constdirectionallightdot = g_constdirectionalightdot; if ( g_numAllowedRootLODs > 0 ) { phdr->numAllowedRootLODs = g_numAllowedRootLODs; } pData = (byte *)phdr + sizeof( studiohdr_t ); // FIXME: Remove when we up the model version phdr->studiohdr2index = ( pData - pStart ); studiohdr2_t* phdr2 = (studiohdr2_t*)pData; memset( phdr2, 0, sizeof(studiohdr2_t) ); pData = (byte*)phdr2 + sizeof(studiohdr2_t); phdr2->illumpositionattachmentindex = g_illumpositionattachment; phdr2->flMaxEyeDeflection = g_flMaxEyeDeflection; BeginStringTable( ); // Copy the full path for compatibility with older programs //V_strcpy_safe( phdr->name, V_UnqualifiedFileName( outname ) ); V_strcpy_safe( phdr->name, outname ); AddToStringTable( phdr2, &phdr2->sznameindex, outname ); WriteBoneInfo( phdr ); if( !g_quiet ) { printf("bones %7d bytes (%d)\n", pData - pStart - total, g_numbones ); } total = pData - pStart; pData = WriteAnimations( pData, pStart, phdr ); if( !g_quiet ) { printf("animations %7d bytes (%d anims) (%d frames) [%d:%02d]\n", pData - pStart - total, g_numani, totalframes, (int)totalseconds / 60, (int)totalseconds % 60 ); } total = pData - pStart; WriteSequenceInfo( phdr ); if( !g_quiet ) { printf("sequences %7d bytes (%d seq) \n", pData - pStart - total, g_sequence.Count() ); } total = pData - pStart; WriteModel( phdr ); /* if( !g_quiet ) { printf("models %7d bytes\n", pData - pStart - total ); } */ total = pData - pStart; WriteTextures( phdr ); if( !g_quiet ) { printf("textures %7d bytes\n", pData - pStart - total ); } total = pData - pStart; WriteKeyValues( phdr, &g_KeyValueText ); if( !g_quiet ) { printf("keyvalues %7d bytes\n", pData - pStart - total ); } total = pData - pStart; WriteBoneTransforms( phdr2, phdr->pBone( 0 ) ); if( !g_quiet ) { printf("bone transforms %7d bytes\n", pData - pStart - total ); } total = pData - pStart; if ( total > FILEBUFFER ) { MdlError( "file exceeds %d bytes (%d)", FILEBUFFER, total ); } WriteBoneFlexDrivers( phdr2 ); if ( !g_quiet ) { printf("bone flex driver %7d bytes\n", pData - pStart - total ); } total = pData - pStart; if ( total > FILEBUFFER ) { MdlError( "file exceeds %d bytes (%d)", FILEBUFFER, total ); } pData = WriteStringTable( pData ); total = pData - pStart; if ( total > FILEBUFFER ) { MdlError( "file exceeds %d bytes (%d)", FILEBUFFER, total ); } phdr->checksum = 0; for (i = 0; i < total; i += 4) { // TODO: does this need something more than a simple shift left and add checksum? phdr->checksum = (phdr->checksum << 1) + ((phdr->checksum & 0x8000000) ? 1 : 0) + *((long *)(pStart + i)); } if (g_bVerifyOnly) return; CollisionModel_Write( phdr->checksum ); if( !g_quiet ) { printf("collision %7d bytes\n", pData - pStart - total ); } AssignMeshIDs( phdr ); phdr->length = pData - pStart; if( !g_quiet ) { printf("total %7d\n", phdr->length ); } if ( phdr->length > FILEBUFFER ) { MdlError( "file exceeds %d bytes (%d)", FILEBUFFER, total ); } // Load materials for this model via the material system so that the // optimizer can ask questions about the materials. LoadMaterials( phdr ); SafeWrite( modelouthandle, pStart, phdr->length ); g_pFileSystem->Close(modelouthandle); if ( spFileModelOut.IsValid() ) spFileModelOut->Add(); if (pBlockStart) { pblockhdr->length = pBlockData - pBlockStart; if ( g_bX360 ) { // Before writing this .ani, write the byteswapped version void *pOutBase = kalloc(1, pblockhdr->length + BYTESWAP_ALIGNMENT_PADDING); int finalSize = StudioByteSwap::ByteswapANI( phdr, pOutBase, pBlockStart, pblockhdr->length ); if ( finalSize == 0 ) { MdlError("Aborted ANI byteswap on '%s':\n", g_animblockname); } char outname[ MAX_PATH ]; Q_StripExtension( g_animblockname, outname, sizeof( outname ) ); Q_strcat( outname, ".360.ani", sizeof( outname ) ); { CP4AutoEditAddFile autop4( outname ); SaveFile( outname, pOutBase, finalSize ); } } SafeWrite( blockouthandle, pBlockStart, pblockhdr->length ); g_pFileSystem->Close( blockouthandle ); if ( spFileBlockOut.IsValid() ) spFileBlockOut->Add(); if ( !g_quiet ) { printf ("---------------------\n"); printf("writing %s:\n", g_animblockname); printf("blocks %7d\n", g_numanimblocks ); printf("total %7d\n", pblockhdr->length ); } } if (phdr->numbodyparts != 0) { // vertices have become an external peer data store // write now prior to impending vertex access from any further code // vertex accessors hide shifting vertex data WriteVertices( phdr ); #ifdef _DEBUG int bodyPartID; for( bodyPartID = 0; bodyPartID < phdr->numbodyparts; bodyPartID++ ) { mstudiobodyparts_t *pBodyPart = phdr->pBodypart( bodyPartID ); int modelID; for( modelID = 0; modelID < pBodyPart->nummodels; modelID++ ) { mstudiomodel_t *pModel = pBodyPart->pModel( modelID ); const mstudio_modelvertexdata_t *vertData = pModel->GetVertexData(); Assert( vertData ); // This can only return NULL on X360 for now int vertID; for( vertID = 0; vertID < pModel->numvertices; vertID++ ) { Vector4D *pTangentS = vertData->TangentS( vertID ); Assert( pTangentS->w == -1.0f || pTangentS->w == 1.0f ); } } } #endif if ( !g_StudioMdlCheckUVCmd.CheckUVs( g_source, g_numsources ) ) { MdlError( "UV checks failed\n" ); } OptimizedModel::WriteOptimizedFiles( phdr, g_bodypart ); // now have external finalized vtx (windings) and vvd (vertexes) // re-open files, sort vertexes, perform fixups, and rewrite // purposely isolated as a post process for stability if (!FixupToSortedLODVertexes( phdr )) { MdlError("Aborted vertex sort fixup on '%s':\n", filename); } if (!Clamp_RootLOD( phdr )) { MdlError("Aborted root lod shift '%s':\n", filename); } } if ( g_bX360 ) { // now all files have been finalized and fixed up. // re-open the files once more and swap all little-endian // data to big-endian format to produce Xbox360 files. WriteAllSwappedFiles( filename ); } // NOTE! If you don't want to go through the effort of loading studiorender for perf reasons, // make sure spewFlags ends up being zero. unsigned int spewFlags = SPEWPERFSTATS_SHOWSTUDIORENDERWARNINGS; if ( g_bPerf ) { spewFlags |= SPEWPERFSTATS_SHOWPERF; } if( spewFlags ) { SpewPerfStats( phdr, filename, spewFlags ); } } const vertexFileHeader_t * mstudiomodel_t::CacheVertexData( void * pModelData ) { static vertexFileHeader_t *pVertexHdr; char filename[MAX_PATH]; Assert( pModelData == NULL ); if (pVertexHdr) { // studiomdl is a single model process, can simply persist data in static goto hasData; } // load and persist the vertex file V_strcpy_safe( filename, gamedir ); // if( *g_pPlatformName ) // { // strcat( filename, "platform_" ); // strcat( filename, g_pPlatformName ); // strcat( filename, "/" ); // } V_strcat_safe( filename, "models/" ); V_strcat_safe( filename, outname ); Q_StripExtension( filename, filename, sizeof( filename ) ); V_strcat_safe( filename, ".vvd" ); LoadFile(filename, (void**)&pVertexHdr); // check id if (pVertexHdr->id != MODEL_VERTEX_FILE_ID) { MdlError("Error Vertex File: '%s' (id %d should be %d)\n", filename, pVertexHdr->id, MODEL_VERTEX_FILE_ID); } // check version if (pVertexHdr->version != MODEL_VERTEX_FILE_VERSION) { MdlError("Error Vertex File: '%s' (version %d should be %d)\n", filename, pVertexHdr->version, MODEL_VERTEX_FILE_VERSION); } hasData: return pVertexHdr; } typedef struct { int meshVertID; int finalMeshVertID; int vertexOffset; int lodFlags; } usedVertex_t; typedef struct { int offsets[MAX_NUM_LODS]; int numVertexes[MAX_NUM_LODS]; } lodMeshInfo_t; typedef struct { usedVertex_t *pVertexList; unsigned short *pVertexMap; int numVertexes; lodMeshInfo_t lodMeshInfo; } vertexPool_t; #define ALIGN(b,s) (((unsigned int)(b)+(s)-1)&~((s)-1)) //----------------------------------------------------------------------------- // FindVertexOffsets // // Iterate sorted vertex list to determine mesh starts and counts. //----------------------------------------------------------------------------- void FindVertexOffsets(int vertexOffset, int offsets[MAX_NUM_LODS], int counts[MAX_NUM_LODS], int numLods, const usedVertex_t *pVertexList, int numVertexes) { int lodFlags; int i; int j; int k; // vertexOffset uniquely identifies a single mesh's vertexes in lod vertex sorted list // lod vertex list is sorted from lod N..lod 0 for (i=numLods-1; i>=0; i--) { offsets[i] = 0; counts[i] = 0; lodFlags = (1<<(i+1))-1; for (j=0; j lodFlags) continue; if (pVertexList[j].vertexOffset != vertexOffset) continue; for (k=j; klodFlags); lodB = Q_log2(pVertexB->lodFlags); // descending sort (LodN..Lod0) sort = lodB-lodA; if (sort) return sort; // within same lod, sub sort (ascending) by mesh sort = pVertexA->vertexOffset - pVertexB->vertexOffset; if (sort) return sort; // within same mesh, sub sort (ascending) by vertex sort = pVertexA->meshVertID - pVertexB->meshVertID; return sort; } //----------------------------------------------------------------------------- // BuildSortedVertexList // // Generates the sorted vertex list. Routine is purposely serial to // ensure vertex integrity. //----------------------------------------------------------------------------- bool BuildSortedVertexList(const studiohdr_t *pStudioHdr, const void *pVtxBuff, vertexPool_t **ppVertexPools, int *pNumVertexPools, usedVertex_t **ppVertexList, int *pNumVertexes) { OptimizedModel::FileHeader_t *pVtxHdr; OptimizedModel::BodyPartHeader_t *pBodyPartHdr; OptimizedModel::ModelHeader_t *pModelHdr; OptimizedModel::ModelLODHeader_t *pModelLODHdr; OptimizedModel::MeshHeader_t *pMeshHdr; OptimizedModel::StripGroupHeader_t *pStripGroupHdr; OptimizedModel::Vertex_t *pStripVertex; mstudiobodyparts_t *pStudioBodyPart; mstudiomodel_t *pStudioModel; mstudiomesh_t *pStudioMesh; usedVertex_t *usedVertexes; vertexPool_t *pVertexPools; vertexPool_t *pPool; usedVertex_t *pVertexList; int *pVertexes; unsigned short *pVertexMap; int index; int currLod; int vertexOffset; int i,j,k,m,n,p; int poolStart; int numVertexPools; int numVertexes; int numMeshVertexes; int offsets[MAX_NUM_LODS]; int counts[MAX_NUM_LODS]; int finalMeshVertID; int baseMeshVertID; *ppVertexPools = NULL; *pNumVertexPools = 0; *ppVertexList = NULL; *pNumVertexes = 0; pVtxHdr = (OptimizedModel::FileHeader_t*)pVtxBuff; // determine number of vertex pools if (pStudioHdr->numbodyparts != pVtxHdr->numBodyParts) return false; numVertexPools = 0; for (i=0; inumBodyParts; i++) { pBodyPartHdr = pVtxHdr->pBodyPart(i); pStudioBodyPart = pStudioHdr->pBodypart(i); if (pStudioBodyPart->nummodels != pBodyPartHdr->numModels) return false; // the model's subordinate lods only reference from a single top level pool // no new verts are created for sub lods // each model's subordinate mesh dictates its own vertex pool for (j=0; jnumModels; j++) { pStudioModel = pStudioBodyPart->pModel(j); numVertexPools += pStudioModel->nummeshes; } } // allocate pools pVertexPools = (vertexPool_t*)malloc(numVertexPools*sizeof(vertexPool_t)); memset(pVertexPools, 0, numVertexPools*sizeof(vertexPool_t)); // iterate lods, mark referenced indexes numVertexPools = 0; for (i=0; inumBodyParts; i++) { pBodyPartHdr = pVtxHdr->pBodyPart(i); pStudioBodyPart = pStudioHdr->pBodypart(i); for (j=0; jnumModels; j++) { pModelHdr = pBodyPartHdr->pModel(j); pStudioModel = pStudioBodyPart->pModel(j); // allocate each mesh's vertex list poolStart = numVertexPools; for (k=0; knummeshes; k++) { // track the expected relative offset into a flattened vertex list vertexOffset = 0; for (m=0; mpMesh(k); numMeshVertexes = pStudioMesh->numvertices; if (numMeshVertexes) { usedVertexes = (usedVertex_t*)malloc(numMeshVertexes*sizeof(usedVertex_t)); pVertexMap = (unsigned short*)malloc(numMeshVertexes*sizeof(unsigned short)); for (n=0; nnumLODs; currLod++) { pModelLODHdr = pModelHdr->pLOD(currLod); if (pModelLODHdr->numMeshes != pStudioModel->nummeshes) return false; for (k=0; knumMeshes; k++) { pMeshHdr = pModelLODHdr->pMesh(k); pStudioMesh = pStudioModel->pMesh(k); for (m=0; mnumStripGroups; m++) { pStripGroupHdr = pMeshHdr->pStripGroup(m); // sanity check the indexes have 100% coverage of the vertexes pVertexes = (int*)malloc(pStripGroupHdr->numVerts*sizeof(int)); memset(pVertexes, 0xFF, pStripGroupHdr->numVerts*sizeof(int)); for (n=0; nnumIndices; n++) { index = *pStripGroupHdr->pIndex(n); if (index < 0 || index >= pStripGroupHdr->numVerts) return false; pVertexes[index] = index; } // sanity check for coverage for (n=0; nnumVerts; n++) { if (pVertexes[n] != n) return false; } free(pVertexes); // iterate vertexes pPool = &pVertexPools[poolStart + k]; for (n=0; nnumVerts; n++) { pStripVertex = pStripGroupHdr->pVertex(n); if (pStripVertex->origMeshVertID < 0 || pStripVertex->origMeshVertID >= pPool->numVertexes) return false; // arrange binary flags for numerical sorting // the lowest detail lod's verts at the top, the root lod's verts at the bottom pPool->pVertexList[pStripVertex->origMeshVertID].lodFlags |= 1<numVertexes; j++) { if (!pPool->pVertexList[j].lodFlags) { // found an orphaned vertex that is unreferenced at any lod strip winding // don't know how these occur or who references them // cannot cull the orphaned vertexes, otherwise vertex counts are wrong // every vertex must be remapped // force the vertex to belong to the lowest lod // lod flags must be nonzero for proper sorted runs pPool->pVertexList[j].lodFlags = 1<<(pVtxHdr->numLODs-1); } } memcpy(&pVertexList[numVertexes], pPool->pVertexList, pPool->numVertexes*sizeof(usedVertex_t)); numVertexes += pPool->numVertexes; } // sort the vertexes based on lod flags // the sort dictates the linear sequencing of the .vvd data file // the vtx file indexes get remapped to the new sort order qsort(pVertexList, numVertexes, sizeof(usedVertex_t), _CompareUsedVertexes); // build a mapping table from mesh relative indexes to the flat lod sorted array vertexOffset = 0; for (i=0; inumVertexes; j++) { // scan flattened sorted vertexes for (k=0; kpVertexMap[j] = k; } vertexOffset += pPool->numVertexes; } // build offsets and counts that identifies mesh's distribution across lods // calc final fixed vertex location if vertexes were gathered to mesh order from lod sorted list finalMeshVertID = 0; poolStart = 0; for (i=0; inumbodyparts; i++) { pStudioBodyPart = pStudioHdr->pBodypart(i); for (j=0; jnummodels; j++) { pStudioModel = pStudioBodyPart->pModel(j); for (m=0; mnummeshes; m++) { // track the expected offset into linear vertexes vertexOffset = 0; for (n=0; nnumLODs, pVertexList, numVertexes); for (n=0; nnumLODs; n++) { if (!counts[n]) offsets[n] = 0; pVertexPools[poolStart+m].lodMeshInfo.offsets[n] = offsets[n]; pVertexPools[poolStart+m].lodMeshInfo.numVertexes[n] = counts[n]; } // iterate using calced offsets to walk each mesh // set its expected final vertex id, which is its "gathered" index relative to mesh baseMeshVertID = finalMeshVertID; for (n=pVtxHdr->numLODs-1; n>=0; n--) { // iterate each vert in the mesh // vertex id is relative to for (p=0; pnummeshes; } } // safety check // every referenced vertex should have been remapped correctly // some models do have orphaned vertexes, ignore these for (i=0; inumLODs != 1) { // file has wrong expected state return false; } // meshes need relocation fixup from lod order back to mesh order numFixups = 0; numMeshes = 0; for (i=0; inumbodyparts; i++) { pStudioBodyPart = pStudioHdr->pBodypart(i); for (j=0; jnummodels; j++) { pStudioModel = pStudioBodyPart->pModel(j); for (k=0; knummeshes; k++) { pStudioMesh = pStudioModel->pMesh(k); if (!pStudioMesh->numvertices) { // no vertexes for this mesh, skip it continue; } for (n=pVtxHdr->numLODs-1; n>=0; n--) { pLodMeshInfo = &pVertexPools[numMeshes+k].lodMeshInfo; if (!pLodMeshInfo->numVertexes[n]) { // no vertexes for this portion of the mesh at this lod, skip it continue; } numFixups++; } } numMeshes += k; } } if (numMeshes == 1 || numFixups == 1 || pVtxHdr->numLODs == 1) { // no fixup required for a single mesh // no fixup required for single lod // no fixup required when mesh data is contiguous as expected numFixups = 0; } pStart_base = (byte*)malloc(FILEBUFFER); memset(pStart_base, 0, FILEBUFFER); pStart_new = (byte*)ALIGN(pStart_base,16); pData_new = pStart_new; // setup headers pFileHdr_new = (vertexFileHeader_t*)pData_new; pData_new += sizeof(vertexFileHeader_t); // clone and fixup new header *pFileHdr_new = *pFileHdr_old; pFileHdr_new->numLODs = pVtxHdr->numLODs; pFileHdr_new->numFixups = numFixups; // skip new fixup table pData_new = (byte*)ALIGN(pData_new, 4); pFixupTable = (vertexFileFixup_t*)pData_new; pFileHdr_new->fixupTableStart = pData_new - pStart_new; pData_new += numFixups*sizeof(vertexFileFixup_t); // skip new vertex data pData_new = (byte*)ALIGN(pData_new, 16); pVertex_new = (mstudiovertex_t*)pData_new; pFileHdr_new->vertexDataStart = pData_new - pStart_new; pData_new += numVertexes*sizeof(mstudiovertex_t); // skip new tangent data pData_new = (byte*)ALIGN(pData_new, 16); pTangent_new = (Vector4D*)pData_new; pFileHdr_new->tangentDataStart = pData_new - pStart_new; pData_new += numVertexes*sizeof(Vector4D); pVertexBase_old = (byte*)pFileHdr_old + pFileHdr_old->vertexDataStart; pTangentBase_old = (byte*)pFileHdr_old + pFileHdr_old->tangentDataStart; // determine number of aggregate verts towards root lod // loader can truncate read according to desired root lod maxCount = -1; for (n=pVtxHdr->numLODs-1; n>=0; n--) { mask = 1<numLODVertexes[n] = maxCount+1; } for (n=pVtxHdr->numLODs; nnumLODVertexes[n] = pFileHdr_new->numLODVertexes[pVtxHdr->numLODs-1]; } // build mesh relocation fixup table if (numFixups) { numMeshes = 0; numOutFixups = 0; for (i=0; inumbodyparts; i++) { pStudioBodyPart = pStudioHdr->pBodypart(i); for (j=0; jnummodels; j++) { pStudioModel = pStudioBodyPart->pModel(j); for (k=0; knummeshes; k++) { pStudioMesh = pStudioModel->pMesh(k); if (!pStudioMesh->numvertices) { // not vertexes for this mesh, skip it continue; } for (n=pVtxHdr->numLODs-1; n>=0; n--) { pLodMeshInfo = &pVertexPools[numMeshes+k].lodMeshInfo; if (!pLodMeshInfo->numVertexes[n]) { // no vertexes for this portion of the mesh at this lod, skip it continue; } pFixupTable[numOutFixups].lod = n; pFixupTable[numOutFixups].numVertexes = pLodMeshInfo->numVertexes[n]; pFixupTable[numOutFixups].sourceVertexID = pLodMeshInfo->offsets[n]; numOutFixups++; } } numMeshes += pStudioModel->nummeshes; } } if (numOutFixups != numFixups) { // logic sync error, final calc should match precalc, otherwise memory corruption return false; } } // generate offsets to vertexes numFlat = 0; pFlatVertexes = (mstudiovertex_t**)malloc(numVertexes*sizeof(mstudiovertex_t*)); pFlatTangents = (Vector4D**)malloc(numVertexes*sizeof(Vector4D*)); for (i=0; inumbodyparts; i++) { pStudioBodyPart = pStudioHdr->pBodypart(i); for (j=0; jnummodels; j++) { pStudioModel = pStudioBodyPart->pModel(j); pVertex_old = (mstudiovertex_t*)&pVertexBase_old[pStudioModel->vertexindex]; pTangent_old = (Vector4D*)&pTangentBase_old[pStudioModel->tangentsindex]; for (k=0; knummeshes; k++) { // get each mesh's vertexes pStudioMesh = pStudioModel->pMesh(k); for (n=0; nnumvertices; n++) { // old vertex pools are per model, seperated per mesh by a start offset // vertexes are then isolated subpools per mesh // build the flat linear array of lookup pointers pFlatVertexes[numFlat] = &pVertex_old[pStudioMesh->vertexoffset + n]; pFlatTangents[numFlat] = &pTangent_old[pStudioMesh->vertexoffset + n]; numFlat++; } } } } // write in lod sorted order for (i=0; ilength = pData_new-pStart_new; { CP4AutoEditAddFile autop4( fileName, "binary" ); SaveFile((char*)fileName, pStart_new, pData_new-pStart_new); } free(pStart_base); free(pFlatVertexes); free(pFlatTangents); // success return true; } //----------------------------------------------------------------------------- // FixupVTXFile // // VTX files get their windings remapped. //----------------------------------------------------------------------------- bool FixupVTXFile(const char *fileName, const studiohdr_t *pStudioHdr, const vertexPool_t *pVertexPools, int numVertexPools, const usedVertex_t *pVertexList, int numVertexes) { OptimizedModel::FileHeader_t *pVtxHdr; OptimizedModel::BodyPartHeader_t *pBodyPartHdr; OptimizedModel::ModelHeader_t *pModelHdr; OptimizedModel::ModelLODHeader_t *pModelLODHdr; OptimizedModel::MeshHeader_t *pMeshHdr; OptimizedModel::StripGroupHeader_t *pStripGroupHdr; OptimizedModel::Vertex_t *pStripVertex; int currLod; int vertexOffset; mstudiobodyparts_t *pStudioBodyPart; mstudiomodel_t *pStudioModel; int i,j,k,m,n; int poolStart; int VtxLen; int newMeshVertID; void *pVtxBuff; VtxLen = LoadFile((char*)fileName, &pVtxBuff); pVtxHdr = (OptimizedModel::FileHeader_t*)pVtxBuff; // iterate all lod's windings poolStart = 0; for (i=0; inumBodyParts; i++) { pBodyPartHdr = pVtxHdr->pBodyPart(i); pStudioBodyPart = pStudioHdr->pBodypart(i); for (j=0; jnumModels; j++) { pModelHdr = pBodyPartHdr->pModel(j); pStudioModel = pStudioBodyPart->pModel(j); // iterate all lods for (currLod=0; currLodnumLODs; currLod++) { pModelLODHdr = pModelHdr->pLOD(currLod); if (pModelLODHdr->numMeshes != pStudioModel->nummeshes) return false; for (k=0; knumMeshes; k++) { // track the expected relative offset into the flat vertexes vertexOffset = 0; for (m=0; mpMesh(k); for (m=0; mnumStripGroups; m++) { pStripGroupHdr = pMeshHdr->pStripGroup(m); for (n=0; nnumVerts; n++) { pStripVertex = pStripGroupHdr->pVertex(n); // remap old mesh relative vertex index to absolute flat sorted list newMeshVertID = pVertexPools[poolStart+k].pVertexMap[pStripVertex->origMeshVertID]; // map to expected final fixed vertex locations // final fixed vertex location is performed by runtime loading code newMeshVertID = pVertexList[newMeshVertID].finalMeshVertID; // fixup to expected pStripVertex->origMeshVertID = newMeshVertID; } } } } poolStart += pStudioModel->nummeshes; } } // pVtxHdr->length = VtxLen; { CP4AutoEditAddFile autop4( fileName, "binary" ); SaveFile((char*)fileName, pVtxBuff, VtxLen); } free(pVtxBuff); return true; } //----------------------------------------------------------------------------- // FixupMDLFile // // MDL files get flexes/vertex/tangent data offsets fixed //----------------------------------------------------------------------------- bool FixupMDLFile(const char *fileName, studiohdr_t *pStudioHdr, const void *pVtxBuff, const vertexPool_t *pVertexPools, int numVertexPools, const usedVertex_t *pVertexList, int numVertexes) { OptimizedModel::FileHeader_t *pVtxHdr; const lodMeshInfo_t *pLodMeshInfo; mstudiobodyparts_t *pStudioBodyPart; mstudiomodel_t *pStudioModel; mstudiomesh_t *pStudioMesh; mstudioflex_t *pStudioFlex; mstudiovertanim_t *pStudioVertAnim; int newMeshVertID; int i; int j; int m; int n; int p; int numLODs; int numMeshes; int total; pVtxHdr = (OptimizedModel::FileHeader_t*)pVtxBuff; numLODs = pVtxHdr->numLODs; numMeshes = 0; for (i=0; inumbodyparts; i++) { pStudioBodyPart = pStudioHdr->pBodypart(i); for (j=0; jnummodels; j++) { pStudioModel = pStudioBodyPart->pModel(j); for (m=0; mnummeshes; m++) { // get each mesh pStudioMesh = pStudioModel->pMesh(m); pLodMeshInfo = &pVertexPools[numMeshes+m].lodMeshInfo; for (n=0; nnumVertexes[p]; // embed the fixup for loader pStudioMesh->vertexdata.numLODVertexes[n] = total; } for (p=n; pvertexdata.numLODVertexes[p] = pStudioMesh->vertexdata.numLODVertexes[numLODs-1]; } // fix the flexes for (n=0; nnumflexes; n++) { pStudioFlex = pStudioMesh->pFlex(n); byte *pvanim = pStudioFlex->pBaseVertanim(); int nVAnimSizeBytes = pStudioFlex->VertAnimSizeBytes(); for (p=0; pnumverts; p++, pvanim += nVAnimSizeBytes ) { pStudioVertAnim = (mstudiovertanim_t*)( pvanim ); if (pStudioVertAnim->index < 0 || pStudioVertAnim->index >= pStudioMesh->numvertices) return false; // remap old mesh relative vertex index to absolute flat sorted list newMeshVertID = pVertexPools[numMeshes+m].pVertexMap[pStudioVertAnim->index]; // map to expected final fixed vertex locations // final fixed vertex location is performed by runtime loading code newMeshVertID = pVertexList[newMeshVertID].finalMeshVertID; // fixup to expected pStudioVertAnim->index = newMeshVertID; } } } numMeshes += pStudioModel->nummeshes; } } { CP4AutoEditAddFile autop4( fileName, "binary" ); SaveFile((char*)fileName, (void*)pStudioHdr, pStudioHdr->length); } // success return true; } //----------------------------------------------------------------------------- // FixupToSortedLODVertexes // // VVD files get vertexes fixed to a flat sorted order, ascending in lower detail lod usage // VTX files get their windings remapped to the sort. //----------------------------------------------------------------------------- bool FixupToSortedLODVertexes(studiohdr_t *pStudioHdr) { char filename[MAX_PATH]; char tmpFileName[MAX_PATH]; void *pVtxBuff; usedVertex_t *pVertexList; vertexPool_t *pVertexPools; int numVertexes; int numVertexPools; int VtxLen; int i; const char *vtxPrefixes[] = {".dx80.vtx", ".dx90.vtx", ".sw.vtx"}; V_strcpy_safe( filename, gamedir ); // if( *g_pPlatformName ) // { // strcat( filename, "platform_" ); // strcat( filename, g_pPlatformName ); // strcat( filename, "/" ); // } V_strcat_safe( filename, "models/" ); V_strcat_safe( filename, outname ); Q_StripExtension( filename, filename, sizeof( filename ) ); // determine lod usage per vertex // all vtx files enumerate model's lod verts, but differ in their mesh makeup // use xxx.dx80.vtx to establish which vertexes are used by each lod V_strcpy_safe( tmpFileName, filename ); V_strcat_safe( tmpFileName, ".dx80.vtx" ); VtxLen = LoadFile( tmpFileName, &pVtxBuff ); // build the sorted vertex tables if (!BuildSortedVertexList(pStudioHdr, pVtxBuff, &pVertexPools, &numVertexPools, &pVertexList, &numVertexes)) { // data sync error return false; } // fixup ???.vvd V_strcpy_safe( tmpFileName, filename ); V_strcat_safe( tmpFileName, ".vvd" ); if (!FixupVVDFile(tmpFileName, pStudioHdr, pVtxBuff, pVertexPools, numVertexPools, pVertexList, numVertexes)) { // data error return false; } for (i=0; i 0xFF) { MdlError("byte conversion out of range %d\n", val ); } return val; } char IsChar( int val ) { if (val < -0x80 || val > 0x7F) { MdlError("char conversion out of range %d\n", val ); } return val; } int IsInt24( int val ) { if (val < -0x800000 || val > 0x7FFFFF) { MdlError("int24 conversion out of range %d\n", val ); } return val; } short IsShort( int val ) { if (val < -0x8000 || val > 0x7FFF) { MdlError("short conversion out of range %d\n", val ); } return val; } unsigned short IsUShort( int val ) { if (val < 0 || val > 0xFFFF) { MdlError("ushort conversion out of range %d\n", val ); } return val; } bool Clamp_MDL_LODS( const char *fileName, int rootLOD ) { studiohdr_t *pStudioHdr; int len; len = LoadFile((char*)fileName, (void **)&pStudioHdr); Studio_SetRootLOD( pStudioHdr, rootLOD ); #if 0 // shift down bone LOD masks int iBone; for ( iBone = 0; iBone < pStudioHdr->numbones; iBone++) { mstudiobone_t *pBone = pStudioHdr->pBone( iBone ); int nLodID; for ( nLodID = 0; nLodID < rootLOD; nLodID++) { int iLodMask = BONE_USED_BY_VERTEX_LOD0 << nLodID; if (pBone->flags & (BONE_USED_BY_VERTEX_LOD0 << rootLOD)) { pBone->flags = pBone->flags | iLodMask; } else { pBone->flags = pBone->flags & (~iLodMask); } } } #endif { CP4AutoEditAddFile autop4( fileName, "binary" ); SaveFile( (char *)fileName, pStudioHdr, len ); } return true; } bool Clamp_VVD_LODS( const char *fileName, int rootLOD ) { vertexFileHeader_t *pTempVvdHdr; int len; len = LoadFile((char*)fileName, (void **)&pTempVvdHdr); int newLength = Studio_VertexDataSize( pTempVvdHdr, rootLOD, true ); // printf("was %d now %d\n", len, newLength ); vertexFileHeader_t *pNewVvdHdr = (vertexFileHeader_t *)calloc( newLength, 1 ); Studio_LoadVertexes( pTempVvdHdr, pNewVvdHdr, rootLOD, true ); if (!g_quiet) { printf ("---------------------\n"); printf ("writing %s:\n", fileName); printf( "vertices (%d vertices)\n", pNewVvdHdr->numLODVertexes[ 0 ] ); } // pNewVvdHdr->length = newLength; { CP4AutoEditAddFile autop4( fileName, "binary" ); SaveFile( (char *)fileName, pNewVvdHdr, newLength ); } return true; } bool Clamp_VTX_LODS( const char *fileName, int rootLOD, studiohdr_t *pStudioHdr ) { int i, j, k, m, n; int nLodID; int size; OptimizedModel::FileHeader_t *pVtxHdr; int len; len = LoadFile((char*)fileName, (void **)&pVtxHdr); OptimizedModel::FileHeader_t *pNewVtxHdr = (OptimizedModel::FileHeader_t *)calloc( FILEBUFFER, 1 ); byte *pData = (byte *)pNewVtxHdr; pData += sizeof( OptimizedModel::FileHeader_t ); ALIGN4( pData ); // header pNewVtxHdr->version = pVtxHdr->version; pNewVtxHdr->vertCacheSize = pVtxHdr->vertCacheSize; pNewVtxHdr->maxBonesPerStrip = pVtxHdr->maxBonesPerStrip; pNewVtxHdr->maxBonesPerTri = pVtxHdr->maxBonesPerTri; pNewVtxHdr->maxBonesPerVert = pVtxHdr->maxBonesPerVert; pNewVtxHdr->checkSum = pVtxHdr->checkSum; pNewVtxHdr->numLODs = pVtxHdr->numLODs; // material replacement list pNewVtxHdr->materialReplacementListOffset = (pData - (byte *)pNewVtxHdr); pData += pVtxHdr->numLODs * sizeof( OptimizedModel::MaterialReplacementListHeader_t ); // ALIGN4( pData ); BeginStringTable( ); // allocate replacement list arrays for ( nLodID = rootLOD; nLodID < pVtxHdr->numLODs; nLodID++ ) { OptimizedModel::MaterialReplacementListHeader_t *pReplacementList = pVtxHdr->pMaterialReplacementList( nLodID ); OptimizedModel::MaterialReplacementListHeader_t *pNewReplacementList = pNewVtxHdr->pMaterialReplacementList( nLodID ); pNewReplacementList->numReplacements = pReplacementList->numReplacements; pNewReplacementList->replacementOffset = (pData - (byte *)pNewReplacementList); pData += pNewReplacementList->numReplacements * sizeof( OptimizedModel::MaterialReplacementHeader_t ); // ALIGN4( pData ); for (i = 0; i < pReplacementList->numReplacements; i++) { OptimizedModel::MaterialReplacementHeader_t *pReplacement = pReplacementList->pMaterialReplacement( i ); OptimizedModel::MaterialReplacementHeader_t *pNewReplacement = pNewReplacementList->pMaterialReplacement( i ); pNewReplacement->materialID = pReplacement->materialID; AddToStringTable( pNewReplacement, &pNewReplacement->replacementMaterialNameOffset, pReplacement->pMaterialReplacementName() ); } } pData = WriteStringTable( pData ); // link previous LODs to higher LODs for ( nLodID = 0; nLodID < rootLOD; nLodID++ ) { OptimizedModel::MaterialReplacementListHeader_t *pRootReplacementList = pNewVtxHdr->pMaterialReplacementList( rootLOD ); OptimizedModel::MaterialReplacementListHeader_t *pNewReplacementList = pNewVtxHdr->pMaterialReplacementList( nLodID ); int delta = (byte *)pRootReplacementList - (byte *)pNewReplacementList; pNewReplacementList->numReplacements = pRootReplacementList->numReplacements; pNewReplacementList->replacementOffset = pRootReplacementList->replacementOffset + delta; } // body parts pNewVtxHdr->numBodyParts = pStudioHdr->numbodyparts; pNewVtxHdr->bodyPartOffset = (pData - (byte *)pNewVtxHdr); pData += pNewVtxHdr->numBodyParts * sizeof( OptimizedModel::BodyPartHeader_t ); // ALIGN4( pData ); // Iterate over every body part... for ( i = 0; i < pStudioHdr->numbodyparts; i++ ) { mstudiobodyparts_t* pBodyPart = pStudioHdr->pBodypart(i); OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart(i); OptimizedModel::BodyPartHeader_t* pNewVtxBodyPart = pNewVtxHdr->pBodyPart(i); pNewVtxBodyPart->numModels = pBodyPart->nummodels; pNewVtxBodyPart->modelOffset = (pData - (byte *)pNewVtxBodyPart); pData += pNewVtxBodyPart->numModels * sizeof( OptimizedModel::ModelHeader_t ); // ALIGN4( pData ); // Iterate over every submodel... for (j = 0; j < pBodyPart->nummodels; ++j) { mstudiomodel_t* pModel = pBodyPart->pModel(j); OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel(j); OptimizedModel::ModelHeader_t* pNewVtxModel = pNewVtxBodyPart->pModel(j); pNewVtxModel->numLODs = pVtxModel->numLODs; pNewVtxModel->lodOffset = (pData - (byte *)pNewVtxModel); pData += pNewVtxModel->numLODs * sizeof( OptimizedModel::ModelLODHeader_t ); ALIGN4( pData ); for ( nLodID = rootLOD; nLodID < pVtxModel->numLODs; nLodID++ ) { OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( nLodID ); OptimizedModel::ModelLODHeader_t *pNewVtxLOD = pNewVtxModel->pLOD( nLodID ); pNewVtxLOD->numMeshes = pVtxLOD->numMeshes; pNewVtxLOD->switchPoint = pVtxLOD->switchPoint; pNewVtxLOD->meshOffset = (pData - (byte *)pNewVtxLOD); pData += pNewVtxLOD->numMeshes * sizeof( OptimizedModel::MeshHeader_t ); ALIGN4( pData ); // Iterate over all the meshes.... for (k = 0; k < pModel->nummeshes; ++k) { Assert( pModel->nummeshes == pVtxLOD->numMeshes ); // mstudiomesh_t* pMesh = pModel->pMesh(k); OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh(k); OptimizedModel::MeshHeader_t* pNewVtxMesh = pNewVtxLOD->pMesh(k); pNewVtxMesh->numStripGroups = pVtxMesh->numStripGroups; pNewVtxMesh->flags = pVtxMesh->flags; pNewVtxMesh->stripGroupHeaderOffset = (pData - (byte *)pNewVtxMesh); pData += pNewVtxMesh->numStripGroups * sizeof( OptimizedModel::StripGroupHeader_t ); // printf("part %d : model %d : lod %d : mesh %d : strips %d : offset %d\n", i, j, nLodID, k, pVtxMesh->numStripGroups, pVtxMesh->stripGroupHeaderOffset ); for (m = 0; m < pVtxMesh->numStripGroups; m++) { OptimizedModel::StripGroupHeader_t *pStripGroup = pVtxMesh->pStripGroup( m ); OptimizedModel::StripGroupHeader_t *pNewStripGroup = pNewVtxMesh->pStripGroup( m ); // int delta = ((byte *)pStripGroup - (byte *)pVtxHdr) - ((byte *)pNewStripGroup - (byte *)pNewVtxHdr); pNewStripGroup->numVerts = pStripGroup->numVerts; pNewStripGroup->vertOffset = (pData - (byte *)pNewStripGroup); size = pNewStripGroup->numVerts * sizeof( OptimizedModel::Vertex_t ); memcpy( pData, pStripGroup->pVertex(0), size ); pData += size; pNewStripGroup->numIndices = pStripGroup->numIndices; pNewStripGroup->indexOffset = (pData - (byte *)pNewStripGroup); size = pNewStripGroup->numIndices * sizeof( unsigned short ); memcpy( pData, pStripGroup->pIndex(0), size ); pData += size; pNewStripGroup->numStrips = pStripGroup->numStrips; pNewStripGroup->stripOffset = (pData - (byte *)pNewStripGroup); size = pNewStripGroup->numStrips * sizeof( OptimizedModel::StripHeader_t ); pData += size; pNewStripGroup->flags = pStripGroup->flags; /* printf("\tnumVerts %d %d :\n", pStripGroup->numVerts, pStripGroup->vertOffset ); printf("\tnumIndices %d %d :\n", pStripGroup->numIndices, pStripGroup->indexOffset ); printf("\tnumStrips %d %d :\n", pStripGroup->numStrips, pStripGroup->stripOffset ); */ for (n = 0; n < pStripGroup->numStrips; n++) { OptimizedModel::StripHeader_t *pStrip = pStripGroup->pStrip( n ); OptimizedModel::StripHeader_t *pNewStrip = pNewStripGroup->pStrip( n ); pNewStrip->numIndices = pStrip->numIndices; pNewStrip->indexOffset = pStrip->indexOffset; pNewStrip->numVerts = pStrip->numVerts; pNewStrip->vertOffset = pStrip->vertOffset; pNewStrip->numBones = pStrip->numBones; pNewStrip->flags = pStrip->flags; pNewStrip->numBoneStateChanges = pStrip->numBoneStateChanges; pNewStrip->boneStateChangeOffset = (pData - (byte *)pNewStrip); size = pNewStrip->numBoneStateChanges * sizeof( OptimizedModel::BoneStateChangeHeader_t ); memcpy( pData, pStrip->pBoneStateChange(0), size ); pData += size; /* printf("\t\tnumIndices %d %d :\n", pNewStrip->numIndices, pNewStrip->indexOffset ); printf("\t\tnumVerts %d %d :\n", pNewStrip->numVerts, pNewStrip->vertOffset ); printf("\t\tnumBoneStateChanges %d %d :\n", pNewStrip->numBoneStateChanges, pNewStrip->boneStateChangeOffset ); */ // printf("(%d)\n", delta ); } // printf("(%d)\n", delta ); } } } } } // Iterate over every body part... for ( i = 0; i < pStudioHdr->numbodyparts; i++ ) { mstudiobodyparts_t* pBodyPart = pStudioHdr->pBodypart(i); // Iterate over every submodel... for (j = 0; j < pBodyPart->nummodels; ++j) { // link previous LODs to higher LODs for ( nLodID = 0; nLodID < rootLOD; nLodID++ ) { OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxHdr->pBodyPart(i)->pModel(j)->pLOD(nLodID); OptimizedModel::ModelLODHeader_t *pRootVtxLOD = pNewVtxHdr->pBodyPart(i)->pModel(j)->pLOD(rootLOD); OptimizedModel::ModelLODHeader_t *pNewVtxLOD = pNewVtxHdr->pBodyPart(i)->pModel(j)->pLOD(nLodID); pNewVtxLOD->numMeshes = pRootVtxLOD->numMeshes; pNewVtxLOD->switchPoint = pVtxLOD->switchPoint; int delta = (byte *)pRootVtxLOD - (byte *)pNewVtxLOD; pNewVtxLOD->meshOffset = pRootVtxLOD->meshOffset + delta; } } } int newLen = pData - (byte *)pNewVtxHdr; // printf("len %d : %d\n", len, newLen ); // pNewVtxHdr->length = newLen; if (!g_quiet) { printf ("writing %s:\n", fileName); printf( "everything (%d bytes)\n", newLen ); } { CP4AutoEditAddFile autop4( fileName, "binary" ); SaveFile( (char *)fileName, pNewVtxHdr, newLen ); } free( pNewVtxHdr ); return true; } bool Clamp_RootLOD( studiohdr_t *phdr ) { char filename[MAX_PATH]; char tmpFileName[MAX_PATH]; int i; const char *vtxPrefixes[] = {".dx80.vtx", ".dx90.vtx", ".sw.vtx"}; int rootLOD = g_minLod; if (rootLOD > g_ScriptLODs.Size() - 1) { rootLOD = g_ScriptLODs.Size() -1; } if (rootLOD == 0) { return true; } V_strcpy_safe( filename, gamedir ); V_strcat_safe( filename, "models/" ); V_strcat_safe( filename, outname ); Q_StripExtension( filename, filename, sizeof( filename ) ); // shift the files so that g_minLod is the root LOD V_strcpy_safe( tmpFileName, filename ); V_strcat_safe( tmpFileName, ".mdl" ); Clamp_MDL_LODS( tmpFileName, rootLOD ); V_strcpy_safe( tmpFileName, filename ); V_strcat_safe( tmpFileName, ".vvd" ); Clamp_VVD_LODS( tmpFileName, rootLOD ); for (i=0; i