You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4586 lines
132 KiB
4586 lines
132 KiB
//========= 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 ); |
|
} |