diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /utils/studiomdl/write.cpp | |
| download | archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip | |
Diffstat (limited to 'utils/studiomdl/write.cpp')
| -rw-r--r-- | utils/studiomdl/write.cpp | 4586 |
1 files changed, 4586 insertions, 0 deletions
diff --git a/utils/studiomdl/write.cpp b/utils/studiomdl/write.cpp new file mode 100644 index 0000000..d3867f4 --- /dev/null +++ b/utils/studiomdl/write.cpp @@ -0,0 +1,4586 @@ +//========= 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 <io.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <limits.h> + +#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; i<pStudioHdr->GetNumSeq(); 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<MAXSTUDIOSEQUENCES; i++) + nSeqOrderRemappingTable[i] = -1; + + bool bAllowSequenceRemoval = false; + + if ( g_vecPreexistingSequences.Count() ) + { + + if ( g_sequence.Count() < g_vecPreexistingSequences.Count() && !bAllowSequenceRemoval ) + { + Msg( "\n" ); + MdlWarning( "This model has fewer sequences than its predecessor.\nPlease confirm sequence deletion: [y/n] " ); + int nInput = 0; + do { nInput = getchar(); } while ( nInput != 121 /* y */ && nInput != 110 /* n */ ); + + if ( nInput == 110 ) + { + MdlError( "Model contains fewer sequences than its predecessor!\n" ); + } + else if ( nInput == 121 ) + { + bAllowSequenceRemoval = true; + } + } + + { + Msg( "Building sequence index remapping table...\n" ); + + CUtlVector<int> 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; nB<g_vecPreexistingSequences.Count(); nB++ ) + { + nSeqOrderRemappingTable[nB] = nSeqOrderRemappingTable[nB+1]; + } + } + while (nSeqOrderRemappingTable[i] == -1); + } + else + { + MdlError( "Failed to reorder sequence indices.\n" ); + } + + } + else if ( nSeqOrderRemappingTable[i] != i ) + { + bUseSeqOrderRemapping = true; + } + } + + if ( bUseSeqOrderRemapping ) + { + Msg( "Sequence indices need re-ordering.\n" ); + } + else + { + Msg( "No re-ordering required.\n" ); + } + } + } + + // build an inverted remapping table so autolayer sequence indices can find their sources later + int nSeqOrderRemappingTableInv[MAXSTUDIOSEQUENCES]; + if ( bUseSeqOrderRemapping ) + { + for (i=0; i<MAXSTUDIOSEQUENCES; i++) + nSeqOrderRemappingTableInv[nSeqOrderRemappingTable[i]] = i; + } + + int m; + for (m = 0; m < g_sequence.Count(); m++, pseqdesc++) + { + + if ( bUseSeqOrderRemapping ) + { + i = nSeqOrderRemappingTable[m]; + if ( i != m ) + { + Msg( " Remapping sequence %i to index %i (%s) to retain existing order.\n", i, m, g_sequence[i].name ); + } + } + else + { + i = m; + } + + byte *pSequenceStart = (byte *)pseqdesc; + + AddToStringTable( pseqdesc, &pseqdesc->szlabelindex, 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; i<pStudioHdr->numbodyparts; i++) + { + pStudioBodyPart = pStudioHdr->pBodypart(i); + for (j=0; j<pStudioBodyPart->nummodels; j++) + { + pStudioModel = pStudioBodyPart->pModel(j); + for (m=0; m<pStudioModel->nummeshes; 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<numVertexes; j++) + { + // determine start of mesh at desired lod + if (pVertexList[j].lodFlags > lodFlags) + continue; + if (pVertexList[j].vertexOffset != vertexOffset) + continue; + + for (k=j; k<numVertexes; k++) + { + // determine end of mesh at desired lod + if (pVertexList[k].vertexOffset != vertexOffset) + break; + if (!(pVertexList[k].lodFlags & (1<<i))) + break; + } + + offsets[i] = j; + counts[i] = k-j; + break; + } + } +} + +//----------------------------------------------------------------------------- +// _CompareUsedVertexes +// +// qsort callback +//----------------------------------------------------------------------------- +static int _CompareUsedVertexes(const void *a, const void *b) +{ + usedVertex_t *pVertexA; + usedVertex_t *pVertexB; + int sort; + int lodA; + int lodB; + + pVertexA = (usedVertex_t*)a; + pVertexB = (usedVertex_t*)b; + + // determine highest (lowest detail) lod + // forces grouping into discrete MAX_NUM_LODS sections + lodA = Q_log2(pVertexA->lodFlags); + 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; i<pVtxHdr->numBodyParts; 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; j<pBodyPartHdr->numModels; 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; i<pVtxHdr->numBodyParts; i++) + { + pBodyPartHdr = pVtxHdr->pBodyPart(i); + pStudioBodyPart = pStudioHdr->pBodypart(i); + + for (j=0; j<pBodyPartHdr->numModels; j++) + { + pModelHdr = pBodyPartHdr->pModel(j); + pStudioModel = pStudioBodyPart->pModel(j); + + // allocate each mesh's vertex list + poolStart = numVertexPools; + for (k=0; k<pStudioModel->nummeshes; k++) + { + // track the expected relative offset into a flattened vertex list + vertexOffset = 0; + for (m=0; m<poolStart+k; m++) + vertexOffset += pVertexPools[m].numVertexes; + + pStudioMesh = pStudioModel->pMesh(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; n<numMeshVertexes; n++) + { + // setup mapping + // due to the hierarchial layout, the vertID's map per mesh's pool + // a linear layout of the vertexes requires a unique signature to achieve a remap + // the offset and index form a unique signature + usedVertexes[n].meshVertID = n; + usedVertexes[n].finalMeshVertID = -1; + usedVertexes[n].vertexOffset = vertexOffset; + usedVertexes[n].lodFlags = 0; + pVertexMap[n] = n; + } + + pVertexPools[numVertexPools].pVertexList = usedVertexes; + pVertexPools[numVertexPools].pVertexMap = pVertexMap; + } + pVertexPools[numVertexPools].numVertexes = numMeshVertexes; + numVertexPools++; + } + + // iterate all lods + for (currLod=0; currLod<pVtxHdr->numLODs; currLod++) + { + pModelLODHdr = pModelHdr->pLOD(currLod); + + if (pModelLODHdr->numMeshes != pStudioModel->nummeshes) + return false; + + for (k=0; k<pModelLODHdr->numMeshes; k++) + { + pMeshHdr = pModelLODHdr->pMesh(k); + pStudioMesh = pStudioModel->pMesh(k); + for (m=0; m<pMeshHdr->numStripGroups; 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; n<pStripGroupHdr->numIndices; n++) + { + index = *pStripGroupHdr->pIndex(n); + if (index < 0 || index >= pStripGroupHdr->numVerts) + return false; + pVertexes[index] = index; + } + + // sanity check for coverage + for (n=0; n<pStripGroupHdr->numVerts; n++) + { + if (pVertexes[n] != n) + return false; + } + + free(pVertexes); + + // iterate vertexes + pPool = &pVertexPools[poolStart + k]; + for (n=0; n<pStripGroupHdr->numVerts; 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<<currLod; + } + } + } + } + } + } + + // flatten the vertex pool hierarchy into a linear sequence + numVertexes = 0; + for (i=0; i<numVertexPools; i++) + numVertexes += pVertexPools[i].numVertexes; + pVertexList = (usedVertex_t*)malloc(numVertexes*sizeof(usedVertex_t)); + numVertexes = 0; + for (i=0; i<numVertexPools; i++) + { + pPool = &pVertexPools[i]; + for (j=0; j<pPool->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; i<numVertexPools; i++) + { + pPool = &pVertexPools[i]; + for (j=0; j<pPool->numVertexes; j++) + { + // scan flattened sorted vertexes + for (k=0; k<numVertexes; k++) + { + if (pVertexList[k].vertexOffset == vertexOffset && pVertexList[k].meshVertID == j) + break; + } + pPool->pVertexMap[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; i<pStudioHdr->numbodyparts; i++) + { + pStudioBodyPart = pStudioHdr->pBodypart(i); + for (j=0; j<pStudioBodyPart->nummodels; j++) + { + pStudioModel = pStudioBodyPart->pModel(j); + for (m=0; m<pStudioModel->nummeshes; m++) + { + // track the expected offset into linear vertexes + vertexOffset = 0; + for (n=0; n<poolStart+m; n++) + vertexOffset += pVertexPools[n].numVertexes; + + // vertexOffset works as unique key to identify vertexes for a specific mesh + // a mesh's verts are distributed, but guaranteed sequential in the lod sorted vertex list + // determine base index and offset and run length for target mesh for all lod levels + FindVertexOffsets(vertexOffset, offsets, counts, pVtxHdr->numLODs, pVertexList, numVertexes); + + for (n=0; n<pVtxHdr->numLODs; 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; p<counts[n]; p++) + { + pVertexList[offsets[n] + p].finalMeshVertID = finalMeshVertID - baseMeshVertID; + finalMeshVertID++; + } + } + } + poolStart += pStudioModel->nummeshes; + } + } + + // safety check + // every referenced vertex should have been remapped correctly + // some models do have orphaned vertexes, ignore these + for (i=0; i<numVertexes; i++) + { + if (pVertexList[i].lodFlags && pVertexList[i].finalMeshVertID == -1) + { + // should never happen, data occured in unknown manner + // don't build corrupted data + return false; + } + } + + // provide generated tables + *ppVertexPools = pVertexPools; + *pNumVertexPools = numVertexPools; + *ppVertexList = pVertexList; + *pNumVertexes = numVertexes; + + // success + return true; +} + +//----------------------------------------------------------------------------- +// FixupVVDFile +// +// VVD files get vertexes remapped to a flat lod sorted order. +//----------------------------------------------------------------------------- +bool FixupVVDFile(const char *fileName, const studiohdr_t *pStudioHdr, const void *pVtxBuff, const vertexPool_t *pVertexPools, int numVertexPools, const usedVertex_t *pVertexList, int numVertexes) +{ + OptimizedModel::FileHeader_t *pVtxHdr; + vertexFileHeader_t *pFileHdr_old; + vertexFileHeader_t *pFileHdr_new; + mstudiobodyparts_t *pStudioBodyPart; + mstudiomodel_t *pStudioModel; + mstudiomesh_t *pStudioMesh; + mstudiovertex_t *pVertex_old; + mstudiovertex_t *pVertex_new; + Vector4D *pTangent_new; + Vector4D *pTangent_old; + mstudiovertex_t **pFlatVertexes; + Vector4D **pFlatTangents; + vertexFileFixup_t *pFixupTable; + const lodMeshInfo_t *pLodMeshInfo; + byte *pStart_new; + byte *pData_new; + byte *pStart_base; + byte *pVertexBase_old; + byte *pTangentBase_old; + void *pVvdBuff; + int i; + int j; + int k; + int n; + int p; + int numFixups; + int numFlat; + int oldIndex; + int mask; + int maxCount; + int numMeshes; + int numOutFixups; + + pVtxHdr = (OptimizedModel::FileHeader_t*)pVtxBuff; + + LoadFile((char*)fileName, &pVvdBuff); + + pFileHdr_old = (vertexFileHeader_t*)pVvdBuff; + if (pFileHdr_old->numLODs != 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; i<pStudioHdr->numbodyparts; i++) + { + pStudioBodyPart = pStudioHdr->pBodypart(i); + for (j=0; j<pStudioBodyPart->nummodels; j++) + { + pStudioModel = pStudioBodyPart->pModel(j); + for (k=0; k<pStudioModel->nummeshes; 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<<n; + for (p=0; p<numVertexes; p++) + { + if (mask & pVertexList[p].lodFlags) + { + if (maxCount < p) + maxCount = p; + } + } + pFileHdr_new->numLODVertexes[n] = maxCount+1; + } + for (n=pVtxHdr->numLODs; n<MAX_NUM_LODS; n++) + { + // ripple the last valid lod entry all the way down + pFileHdr_new->numLODVertexes[n] = pFileHdr_new->numLODVertexes[pVtxHdr->numLODs-1]; + } + + // build mesh relocation fixup table + if (numFixups) + { + numMeshes = 0; + numOutFixups = 0; + for (i=0; i<pStudioHdr->numbodyparts; i++) + { + pStudioBodyPart = pStudioHdr->pBodypart(i); + for (j=0; j<pStudioBodyPart->nummodels; j++) + { + pStudioModel = pStudioBodyPart->pModel(j); + for (k=0; k<pStudioModel->nummeshes; 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; i<pStudioHdr->numbodyparts; i++) + { + pStudioBodyPart = pStudioHdr->pBodypart(i); + for (j=0; j<pStudioBodyPart->nummodels; j++) + { + pStudioModel = pStudioBodyPart->pModel(j); + pVertex_old = (mstudiovertex_t*)&pVertexBase_old[pStudioModel->vertexindex]; + pTangent_old = (Vector4D*)&pTangentBase_old[pStudioModel->tangentsindex]; + for (k=0; k<pStudioModel->nummeshes; k++) + { + // get each mesh's vertexes + pStudioMesh = pStudioModel->pMesh(k); + for (n=0; n<pStudioMesh->numvertices; 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; i<numVertexes; i++) + { + // iterate sorted order, remap old vert location to new vert location + oldIndex = pVertexList[i].vertexOffset + pVertexList[i].meshVertID; + + memcpy(&pVertex_new[i], pFlatVertexes[oldIndex], sizeof(mstudiovertex_t)); + memcpy(&pTangent_new[i], pFlatTangents[oldIndex], sizeof(Vector4D)); + } + + // pFileHdr_new->length = 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; i<pVtxHdr->numBodyParts; i++) + { + pBodyPartHdr = pVtxHdr->pBodyPart(i); + pStudioBodyPart = pStudioHdr->pBodypart(i); + + for (j=0; j<pBodyPartHdr->numModels; j++) + { + pModelHdr = pBodyPartHdr->pModel(j); + pStudioModel = pStudioBodyPart->pModel(j); + + // iterate all lods + for (currLod=0; currLod<pVtxHdr->numLODs; currLod++) + { + pModelLODHdr = pModelHdr->pLOD(currLod); + + if (pModelLODHdr->numMeshes != pStudioModel->nummeshes) + return false; + + for (k=0; k<pModelLODHdr->numMeshes; k++) + { + // track the expected relative offset into the flat vertexes + vertexOffset = 0; + for (m=0; m<poolStart+k; m++) + vertexOffset += pVertexPools[m].numVertexes; + + pMeshHdr = pModelLODHdr->pMesh(k); + for (m=0; m<pMeshHdr->numStripGroups; m++) + { + pStripGroupHdr = pMeshHdr->pStripGroup(m); + + for (n=0; n<pStripGroupHdr->numVerts; 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; i<pStudioHdr->numbodyparts; i++) + { + pStudioBodyPart = pStudioHdr->pBodypart(i); + + for (j=0; j<pStudioBodyPart->nummodels; j++) + { + pStudioModel = pStudioBodyPart->pModel(j); + + for (m=0; m<pStudioModel->nummeshes; m++) + { + // get each mesh + pStudioMesh = pStudioModel->pMesh(m); + pLodMeshInfo = &pVertexPools[numMeshes+m].lodMeshInfo; + + for (n=0; n<numLODs; n++) + { + // the root lod, contains all the lower detail lods verts + // tally the verts that are at each lod + total = 0; + for (p=n; p<numLODs; p++) + total += pLodMeshInfo->numVertexes[p]; + + // embed the fixup for loader + pStudioMesh->vertexdata.numLODVertexes[n] = total; + } + for (p=n; p<MAX_NUM_LODS; p++) + { + // duplicate last valid lod to end of list + pStudioMesh->vertexdata.numLODVertexes[p] = pStudioMesh->vertexdata.numLODVertexes[numLODs-1]; + } + + // fix the flexes + for (n=0; n<pStudioMesh->numflexes; n++) + { + pStudioFlex = pStudioMesh->pFlex(n); + + byte *pvanim = pStudioFlex->pBaseVertanim(); + int nVAnimSizeBytes = pStudioFlex->VertAnimSizeBytes(); + + for (p=0; p<pStudioFlex->numverts; 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<ARRAYSIZE(vtxPrefixes); i++) + { + // fixup ???.vtx + V_strcpy_safe( tmpFileName, filename ); + V_strcat_safe( tmpFileName, vtxPrefixes[i] ); + if (!FixupVTXFile(tmpFileName, pStudioHdr, pVertexPools, numVertexPools, pVertexList, numVertexes)) + { + // data error + return false; + } + } + + // fixup ???.mdl + V_strcpy_safe( tmpFileName, filename ); + V_strcat_safe( tmpFileName, ".mdl" ); + if (!FixupMDLFile(tmpFileName, pStudioHdr, pVtxBuff, pVertexPools, numVertexPools, pVertexList, numVertexes)) + { + // data error + return false; + } + + // free the tables + for (i=0; i<numVertexPools; i++) + { + if (pVertexPools[i].pVertexList) + free(pVertexPools[i].pVertexList); + if (pVertexPools[i].pVertexMap) + free(pVertexPools[i].pVertexMap); + } + if (numVertexPools) + free(pVertexPools); + free(pVtxBuff); + + // success + return true; +} + + +byte IsByte( int val ) +{ + if (val < 0 || val > 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<ARRAYSIZE(vtxPrefixes); i++) + { + // fixup ???.vtx + V_strcpy_safe( tmpFileName, filename ); + V_strcat_safe( tmpFileName, vtxPrefixes[i] ); + Clamp_VTX_LODS( tmpFileName, rootLOD, phdr ); + } + + return true; +} + + +//---------------------------------------------------------------------- +// For a particular .qc, converts all studiomdl generated files to big-endian format. +//---------------------------------------------------------------------- +void WriteSwappedFile( char *srcname, char *outname, int(*pfnSwapFunc)(void*, const void*, int) ) +{ + if ( FileExists( srcname ) ) + { + if( !g_quiet ) + { + printf( "---------------------\n" ); + printf( "Generating Xbox360 file format for \"%s\":\n", srcname ); + } + + void *pFileBase = NULL; + int fileSize = LoadFile( srcname, &pFileBase ); + int paddedSize = fileSize + BYTESWAP_ALIGNMENT_PADDING; + + void *pOutBase = malloc( paddedSize ); + + int bytes = pfnSwapFunc( pOutBase, pFileBase, fileSize ); + + if ( bytes != 0 ) + { + CP4AutoEditAddFile autop4( outname, "binary" ); + SaveFile( outname, pOutBase, bytes ); + } + + free(pOutBase); + free(pFileBase); + + if ( bytes == 0 ) + { + MdlError( "Aborted byteswap on '%s':\n", srcname ); + } + } +} + +//---------------------------------------------------------------------- +// For a particular .qc, converts all studiomdl generated files to big-endian format. +//---------------------------------------------------------------------- +void WriteAllSwappedFiles( const char *filename ) +{ + char srcname[ MAX_PATH ]; + char outname[ MAX_PATH ]; + + extern IPhysicsCollision *physcollision; + if ( physcollision ) + { + StudioByteSwap::SetCollisionInterface( physcollision ); + } + + // Convert PHY + Q_StripExtension( filename, srcname, sizeof( srcname ) ); + Q_strncpy( outname, srcname, sizeof( outname ) ); + + Q_strcat( srcname, ".phy", sizeof( srcname ) ); + Q_strcat( outname, ".360.phy", sizeof( outname ) ); + + WriteSwappedFile( srcname, outname, StudioByteSwap::ByteswapPHY ); + + // Convert VVD + Q_StripExtension( filename, srcname, sizeof( srcname ) ); + Q_strncpy( outname, srcname, sizeof( outname ) ); + + Q_strcat( srcname, ".vvd", sizeof( srcname ) ); + Q_strcat( outname, ".360.vvd", sizeof( outname ) ); + + WriteSwappedFile( srcname, outname, StudioByteSwap::ByteswapVVD ); + + // Convert VTX + Q_StripExtension( filename, srcname, sizeof( srcname ) ); + Q_StripExtension( srcname, srcname, sizeof( srcname ) ); + Q_strncpy( outname, srcname, sizeof( outname ) ); + + Q_strcat( srcname, ".dx90.vtx", sizeof( srcname ) ); + Q_strcat( outname, ".360.vtx", sizeof( outname ) ); + + WriteSwappedFile( srcname, outname, StudioByteSwap::ByteswapVTX ); + + // Convert MDL + Q_StripExtension( filename, srcname, sizeof( srcname ) ); + Q_strncpy( outname, srcname, sizeof( outname ) ); + + Q_strcat( srcname, ".mdl", sizeof( srcname ) ); + Q_strcat( outname, ".360.mdl", sizeof( outname ) ); + + WriteSwappedFile( srcname, outname, StudioByteSwap::ByteswapMDL ); +}
\ No newline at end of file |